New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@cara/porter

Package Overview
Dependencies
Maintainers
2
Versions
154
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cara/porter

A koa and express middleware for browser side javascript module authoring.

  • 1.0.0-alpha.3
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
75
decreased by-44.44%
Maintainers
2
Weekly downloads
 
Created
Source

Porter

NPM Downloads NPM Version Build Status

porter is a JS/CSS module loader featuring module transformation on the fly.

Options

cacheExcept=[]

To accelerate loading in development mode, Porter will cache node_modules by compiling and bundling them on the fly. You can rule out some of them by passing an array of module names to cacheExcept option:

app.use(porter({ cacheExcept: 'mobx' }))
app.use(porter({ cacheExcept: ['mobx', 'react'] }))

To turn off the node_modules caching completely, just set cacheExcept to *:

app.use(porter({ cacheExcept: '*' }))

cachePersist=true

porter will not clear the cache (except the ones specified in cacheExcept option) by default. Set cachePersist to false to make porter clear cache every time it restarts:

app.use(porter({ cachePersist: false }))

dest='public'

Porter caches node_modules compilations, js components transformations (if .babelrc exists), and stylesheets. Set dset=other/directory to store the cache somewhere else:

app.use(porter({ dest: '.porter-cache' }))

Some of the cache requires a static serving middleware to work:

  • node_modules compilation results,
  • components source maps generated after transformation.

For Koa:

app.use(require('koa-static')(path.join(__dirname, 'public')))
app.use(requrie('porter')({ dest: 'public' }))

For Express:

app.use(express.static(path.join(__dirname, 'public')))
app.use(requrie('porter')())

express=false

porter() returns a koa middleware by default. Set express=true to get an express middleware instead:

app.use(require('@cara/porter')({ express: true }))

loaderConfig={}

There's a loader hidden in Porter which is the magic behind Porter that makes module loading possible. When js entries such as app.js?main is requested, Porter will prepend the loader and loader config to the content of the component. See the loader section for detailed information.

mangleExcept=[]

While porter caches node_modules, the code will be bundled and minified with UglifyJS. In rare caces, UglifyJS' name mangling might generate false results, which can be bypassed with mangleExcept:

app.use(porter({ mangleExcept: ['react-router'] }))

paths='components'

The directory of your components. Multiple paths is allowd. For example, you need to import modules from both the components directory of your app and node_modules/@corp/sharedComponents:

app.use(porter({
  paths: [ 'components', 'node_modules/@corp/sharedComponents']
}))

root=process.cwd()

Normally this option should never be used. Options like paths and dest are all resolved against root. In test cases like tests/test.index.js in the source code, we need to change the root to path.join(__dirname, 'test/example').

serveSource=false

Porter generates source maps while transforming components, caching node_modules, or compiling the final assets. For content security concerns, the sourceContents are removed in the generated source maps and a sourceRoot is set instead. In this way, porter won't leak any source code by default. And if you do need source code being fetched by browser, you can simply turn on serveSource:

app.use(porter({ serveSource: true }))
// or set it in a more recommended way
app.use(porter({ serveSource: process.env.NODE_ENV == 'development' }))

transformOnly=[]

Besides components, Porter can also transform node_modules. Simply put the module names in transformOnly:

app.use(porter({ transformOnly: ['some-es6-module'] }))

If the module being loaded is listed in transformOnly, and a .babelrc within the module directory is found, porter will process the module source with babel too, like the way it handles components. Don't forget to install the presets and plugins listed in the module's .babelrc .

Deployment

Oceanfiy provides two static methods for assets precompilation:

  • porter.compileAll()
  • porter.compileStyleSheets()

.compileAll([options])

.compileAll([options]) returns a Promise.

const porter = require('@cara/porter')

// Specify the entry modules
porter.compileAll({ match: 'app.js' })
  .then(function() {
    console.log('done')
  })
  .catch(function(err) {
    console.error(err.stack)
  })

// You can omit the options since they're the defaults.
porter.compileAll()

Porter will compile all the components that matches opts.match, find their dependencies in node_modules directory and compile them too.

You can try the one in Porter Example. Just execute npm run precompile.

.compileStyleSheets([options])

.compileStyleSheets([options]) returns a Promise.

const porter = require('@cara/porter')

porter.compileStyleSheets({ match: 'app.css' })
  .then(function() {
    console.log('done')
  })
  .catch(function() {
    console.error(err.stack)
  })

Currently .compileStyleSheets just process the source code with autoprefixer and postcss-import. You gonna need some minification tools likecssnano to minify the compiled result.

Behind the Scene

Let's start with app.js, which might seems a bit of black magic at the first glance. It is added to the page directly:

<script src="/app.js?main"></script>

And suddenly you can write app.js as CommonJS or ES Module right away:

const React = require('react')
import mobx from 'mobx'

How can browser know where to require when executing main.js?

Loader

The secret is, entry components that ends with ?main (e.g. app.js?main) will be prepended with two things before the the actual app.js when it's served with Porter:

  1. Loader
  2. Loader config

You can import app.js explicitly if you prefer:

<script src="/loader.js"></script>
<script>porter.import('app')</script>

Both way works. To make app.js consumable by the Loader, it will be wrapped into Common Module Declaration format on the fly:

define(id, deps, function(require, exports, module) {
  // actual main.js content
})
  • id is deducted from the file path.
  • dependencies is parsed from the factory code thanks to the match-require module.
  • factory (the anonymouse function) body is left untouched or transformed with babel depending on whether .babelrc exists or not.

If ES Module is preferred, you'll need two things:

  1. Put a .babelrc file under your components directory.
  2. Install the presets or plugins configured in said .babelrc.

Back to the Loader, after the wrapped app.js is fetched, it won't execute right away. The dependencies need to be resolved first. For relative dependencies (e.g. other components), it's easy to just resolve them against id. For external dependencies (in this case, react and mobx), there's more work done by Porter under the hood:

  1. Generate a dependencies map by parsing components and node_modules when it initializes,
  2. Flatten the dependencies map into a list of modules required (directly or indirectly) by current entry,
  3. Config the loader with the list (among other loader config).

Take heredoc's (simplified) node_modules for example:

➜  heredoc git:(master) ✗ tree node_modules -I "mocha|standard"
node_modules
└── should
    ├── index.js
    ├── node_modules
    │   └── should-type
    │       ├── index.js
    │       └── package.json
    └── package.json

It will be flattened into:

{
  "should": {
    "6.0.3": {
      "main": "./lib/should.js",
      "dependencies": {
        "should-type": "0.0.4"
      }
    }
  },
  "should-type": {
    "0.0.4": {}
  }
}

The original dependency path should/should-type is now at the same level of should. There still are dependencies, to store the actual version of should/should-type required by should. Notice this structure supports multiple versions.

Loader Config

The structure is then put among other options passed to Loader with porter.config():

porter.config({
  "base": "http://localhost:5000",
  "name": "heredoc",
  "version": "1.3.1",
  "main": "index",
  "modules": { ... }
})
  • base is the root path of components and node modules.
  • name, version, and main are self-explanatory. They are all extracted from package.json of the app.
  • modules is the flattened dependencies map.

Wrap It Up

So here is app.js?main expanded:

// GET /loader.js returns both Loader and Loader Config.
;(function() { /* Loader */ })
porter.config({ /* Loader Config */})

// The module definition and the import kick off.
define(id, dependencies, function(require, exports, module) { /* app.js */ })
porter.import('app')

Here's the actual interaction between browser and backend:

  1. Browser requests /app.js?main;
  2. Porter prepares the content of /app.js?main with Loader, Loader Config, and the wrapped app.js;
  3. Browser executes the returned /app.js, Loader kicks in, cache app.js module in registry;
  4. Loader resolves the dependencies of app.js module;
  5. Browser requests the dependencies per Loader's request;
  6. Loader executes the factory of app.js once all the dependencies are resolved.

StyleSheets

The stylesheets part is much easier since Porter does not provide a CSS Loader for now. All of the @imports are handled at the backend. Take following app.css for example:

@import "cropper/dist/cropper.css";
@import "common.css"

body {
  padding: 50px;
}

When browser requests app.css:

  1. postcss-import processes all of the @imports;
  2. autoprefixer transforms the bundle;

Voila!

FAQs

Package last updated on 31 Jan 2018

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc