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
3
Versions
154
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cara/porter - npm Package Compare versions

Comparing version 4.2.14 to 4.3.0

Readme.zh-CN.md

13

loader.js

@@ -159,12 +159,13 @@ /* eslint-env browser */

if (!context) throw new Error('context module of ' + uri + ' not found');
// execute context module factory for the first time to grab the imports
context.execute();
// prepare the imports of wasm module
var imports = {};
imports['./' + contextId.split('/').pop()] = context.exports;
// loader.js might be required to run in legacy browser hence async/await not used
fetch(new URL(uri, location.origin))
.then(function onResponse(module) {
// execute context module factory for the first time to grab the imports
// FIXME: context might not be ready to execute if the packet weren't bundled
context.execute();
// prepare the imports of wasm module
var imports = {};
imports['./' + contextId.split('/').pop()] = context.exports;
return loadWasm(module, imports);

@@ -171,0 +172,0 @@ })

{
"name": "@cara/porter",
"description": "A middleware for web modules",
"version": "4.2.14",
"version": "4.3.0",
"main": "src/porter.js",

@@ -46,3 +46,3 @@ "repository": {

"license": "BSD-3-Clause",
"gitHead": "168e9b96c1cfbd00ec482ba20c99efc034d57b2c"
"gitHead": "69a3fbeb6532552ad13c915a325329463e94a966"
}

@@ -7,13 +7,15 @@ # Porter

Porter is a **consolidated browser module solution** which provides a module system for web browsers that is both CommonJS and [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) compatible.
[中文版](./Readme.zh-CN.md)
Here are the features that make Porter different from (if not better than) other module solutions:
Porter is a **consolidated browser module solution** which provides a module system for web browsers that is both CommonJS and [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) compatible, with following features supported:
1. Both synchronous and asynchronous module loading are supported. `import` is transformed with either Babel or TypeScript. `import()` is not fully supported yet but there's an equivalent `require.async(specifier, mod => {})` provided.
2. Implemented with the concept `Module` (file) and `Package` (directory with package.json and files) built-in.
3. Fast enough module resolution and transpilation that makes the `watch => bundle` loop unnecessary. With Porter the middleware, `.css` and `.js` requests are intercepted (and processed if changed) correspondingly.
1. Both synchronous and asynchronous module loading are supported, which means `require()`, `require.async()`, `import`, or `import()` can be used at will to request modules, dynamically imported modules will be bundled separately.
2. Bundle at package level, or bundle everything, it's completely up to you.
3. Fast enough module resolution and transpilation, with reasonable levels of cache that makes production builds more effecient.
It is recommended to first start with our [starter](https://porterhq.github.io/porter/starter) documentation or the thorough [user guides](https://porterhq.github.io/porter/basics).
## Setup
> This document is mainly about Porter the middleware. To learn about Porter CLI, please visit the [corresponding folder](https://github.com/porterhq/porter/packages/porter-cli).
> This document is mostly about Porter the middleware. To learn about Porter CLI, please visit the [corresponding folder](https://github.com/porterhq/porter/tree/master/packages/porter-cli) or the [Porter Documentation](https://porterhq.github.io/porter).

@@ -36,3 +38,3 @@ Porter the middleware is compatible with Koa (both major versions) and Express:

With the default setup, browser modules at `./components` folder is now accessible with `/path/to/file.js` or `/${pkg.name}/${pkg.version}/path/to/file.js`. Take [demo-cli](https://github.com/porterhq/porter/packages/demo-cli) for example, the file structure shall resemble that of below:
With the default setup, browser modules at `./components` folder is now accessible with `/path/to/file.js`. Take [demo-cli](https://github.com/porterhq/porter/tree/master/packages/demo-cli) for example, the file structure shall resemble that of below:

@@ -62,3 +64,3 @@ ```bash

<meta charset="utf-8">
<title>An Porter Demo</title>
<title>A Porter Demo</title>
<!-- CSS entry -->

@@ -75,10 +77,16 @@ <link rel="stylesheet" type="text/css" href="/app.css">

The extra `?main` querystring might seem a bit confusing at first glance. It tells the porter middleware to bundle loader when `/app.js?main` is accessed. The equivalent `<script>` entry of above is:
> The extra `?main` parameter in the JavaScript entry query is added for historical reasons. It tells the porter middleware to include loader.js when bundling app.js, which isn't necessary if loader.js is included explicitly:
>
> ```html
> <!-- entry format 1 -->
> <script src="/loader.js" data-main="app.js"></script>
> <!-- entry format 2 -->
> <script src="/loader.js"></script>
> <script>porter.import('app')</script>
> ```
>
> Both formats are no longer recommended, please use `<script src="/app.js?main"></script>` directly.
```html
<script src="/loader.js" data-main="app.js"></script>
```
In JavaScript entry, all kinds of imports are supported:
Both `<script>`s work as the JavaScript entry of current page. In `./components/app.js`, there are the good old `require` and `exports`:
```js

@@ -88,2 +96,11 @@ import $ from 'jquery'; // => ./node_modules/jquery/dist/jquery.js

import util from './util'; // => ./components/util.js or ./components/util/index.js
// <link rel="stylesheet" type="text/css" href="/app.js"> is still needed though
import './foo.css';
// will fetch the wasm file, instantiate it, and return the exports
import wasm from './foo.wasm';
// will bundle and fetch worker.js separately
import Worker from 'worker-loader!./worker.js';
```

@@ -100,4 +117,83 @@

<https://www.yuque.com/porterhq/porter/fitqkz>
In a nutshell, here is the list of porter options:
```javascript
const path = require('path');
const Porter = require('@cara/porter');
const porter = new Porter({
// project root, defaults to `process.cwd()`
root: process.cwd(),
// paths of browser modules, or components, defaults to `'components'`
paths: 'components',
// output settings
output: {
// path of the compile output, defaults to `'public'`
path: 'public',
},
// cache settings
cache: {
// path of the cache store, defaults to `output.path`
path: '.porter-cache',
// cache identifier to shortcut cache invalidation
identifier({ packet }) {
return JSON.stringify([
require('@cara/porter/package.json').version,
packet.transpiler,
packet.transpilerVersion,
packet.transpilerOpts,
]);
},
},
// preload common dependencies, defaults to `[]`
preload: [ 'preload', '@babel/runtime' ],
// the module resolution behaviour
resolve: {
// an alias at project level to simplify import specifier, such as
// import util from '@/util'; // => components/util/index.js
alias: {
'@': path.join(process.cwd(), 'components'),
},
// supported extensions
extensions: [ '*', '.js', '.jsx', '.ts', '.tsx', '.css' ],
// transform big libraries that support partial import by conventions
import: [
{ libraryName: 'antd', style: 'css' },
{ libraryName: 'lodash',
libraryDirectory: '',
camel2DashComponentName: false },
],
},
// transpile settings
transpile: {
// turn on transpilation on certain dependencies, defaults to `[]`
include: [ 'antd' ],
},
// bundle settings
bundle: {
// excluded dependencies will be bundled separately, defaults to `[]`
exclude: [ 'antd' ],
},
// source settings
source: {
// serve the source file if it's development mode, defaults to `false`
serve: process.env.NODE_ENV !== 'production',
// the `sourceRoot` in the generated source map, defaults to `'/'`
root: 'localhost:3000',
},
});
```
## Deployment

@@ -108,9 +204,9 @@

```js
const porter = new Porter()
const porter = new Porter({
output: { path: 'dist' },
});
porter.compileAll({
await porter.compileAll({
entries: ['app.js', 'app.css']
})
.then(() => console.log('done')
.catch(err => console.error(err.stack))
});
```

@@ -121,28 +217,16 @@

- Entries are bundled separately, e.g. `entries: ['app.js', 'app2.js']` are compiled into two different bundles.
- Dependencies are bundled per package with internal modules put together, e.g. jQuery gets compiled as `jquery/3.3.1/dist/jquery.js`.
- Dependencies with multiple entries gets bundled per package as well, e.g. lodash methods will be compiled as `lodash/4.17.10/~bundle-36bdcd6d.js`.
- Dependencies are bundled per package with internal modules put together, e.g. jQuery gets compiled as `jquery/3.3.1/dist/jquery.4f8208b0.js`.
- Dependencies with multiple entries gets bundled per package as well, e.g. lodash methods will be compiled as `lodash/4.17.10/lodash.36bdcd6d.js`.
Assume the root package is:
Take following app.js for example:
```json
{
"name": "@cara/demo-cli",
"version": "2.0.0"
}
```
and the content of `./components/app.js` is:
```js
'use strict'
const $ = require('jquery')
const throttle = require('lodash/throttle')
const camelize = require('lodash/camelize')
const util = require('./util')
import $ from 'jquery';
import throttle from 'lodash/throttle';
import camelize from 'lodash/camelize';
import util from './util';
// code
```
After `porter.compileAll({ entries: ['app.js'] })`, the files in `./public` should be:
When `porter.compileAll({ entries: ['app.js'] })` is done, the output files should be:

@@ -173,147 +257,1 @@ ```bash

```
## Behind the Scene
Let's start with `app.js`, which might seem a bit confusing at the first glance. It is added to the page directly:
```html
<script src="/app.js?main"></script>
```
And suddenly you can write `app.js` as Node.js Modules or ES Modules right away:
```js
import mobx from 'mobx'
const React = require('react')
```
How can browser know where to `import` MobX or `require` React when executing `app.js`?
### Loader
The secret is, entries that has `main` in the querystring (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. Package lock
You can import `app.js` explicitly if you prefer:
```html
<script src="/loader.js"></script>
<script>porter.import('app')</script>
<!-- or with shortcut -->
<script src="/loader.js" data-main="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:
```js
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 with [js-tokens](https://github.com/lydell/js-tokens).
- `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. dependencies within the same package), it's easy to just resolve them against `module.id`. For external dependencies (in this case, react and mobx), `node_modules` are looked.
The parsed dependencies is in two trees, one for modules (file by file), one for packages (folder by folder). When the entry module (e.g. `app.js`) is accessed, a package lock is generated and prepended before the module to make sure the correct module path is used.
Take heredoc's (simplified) node_modules for example:
```bash
➜ 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:
```js
{
"should": {
"6.0.3": {
"main": "./lib/should.js",
"dependencies": {
"should-type": "0.0.4"
}
}
},
"should-type": {
"0.0.4": {}
}
}
```
### Loader Config
Besides package lock, there're several basic loader settings (which are all configurable while `new Porter()`):
| property | description |
|-----------|-------------|
| `baseUrl` | root path of the browser modules, e.g. `https://staticfile.org/` |
| `map` | module mappings that may interfere module resolution |
| `package` | metadata of the root package, e.g. `{ name, version, main, entries }` |
| `preload` | a syntax sugar for quick loading certain files before entry |
In development phase, Porter configs the loader with following settings:
```js
{
baseUrl: '/',
package: { /* generated from package.json of the project */ }
}
```
### Wrap It Up
So here is `app.js?main` expanded:
```js
// GET /loader.js returns both Loader and Loader Config.
;(function() { /* Loader */ })
Object.assign(porter.lock, /* package lock */)
// 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 Porter:
![](https://cdn.yuque.com/__puml/76189ffa06e35b64edd55c3e9423734d.svg)
### StyleSheets
The stylesheets part is much easier since Porter processes CSS `@import`s at the first place. Take following `app.css` for example:
```css
@import "cropper/dist/cropper.css";
@import "common.css"
body {
padding: 50px;
}
```
![](https://cdn.yuque.com/__puml/5c1a7b8ae1312893829aaf4f357cdadd.svg)
When browser requests `app.css`:
1. `postcss-import` processes all of the `@import`s;
2. `autoprefixer` transforms the bundle;
Porter then responses with the processed CSS (which has all `@import`s replaced with actual file contents).
'use strict';
const glob = require('glob');
// https://github.com/junosuarez/heredoc/blob/master/index.js

@@ -20,10 +22,15 @@ const stripPattern = /^[ \t]*(?=[^\s]+)/mg;

/**
* @typedef { import("@babel/core").NodePath } NodePath
* @param {Object} options
* @param { import("@babel/types")} options.types
* @param { import("@babel/template")} options.template
* @returns {Object}
*/
module.exports = function({ types: t, template }) {
let globIndex = 0;
module.exports = function({ types: t }) {
const visitor = {
/**
* Remove `require('heredoc')`
* @param {NodePath} path
* @param {import("@babel/core").NodePath} path
* @param {import('@babel/core').PluginPass} state
*/

@@ -41,3 +48,4 @@ VariableDeclaration(path) {

* Transform `heredoc(function() {/* text ...})` to text.
* @param {NodePath} path
* @param {import("@babel/core").NodePath} path
* @param {import('@babel/core').PluginPass} state
*/

@@ -55,10 +63,44 @@ CallExpression(path) {

/**
* Transform `import.meta` to `__module.meta`
* @param {NodePath} path
* Transform `import.meta.url` to `__module.meta.url`
* Transform `import.meta.glob(pattern, options)` like vite https://vitejs.dev/guide/features.html#glob-import
* @param {import("@babel/core").NodePath} path
* @param {import('@babel/core').PluginPass} state
*/
MetaProperty(path) {
const { node } = path;
if (node.meta && node.meta.name === 'import' &&
node.property.name === 'meta') {
path.replaceWithSourceString('__module.meta');
MetaProperty(path, state) {
if (t.isMemberExpression(path.parent) && path.parent.property.name === 'url') {
path.replaceWith(t.memberExpression(t.identifier('__module'), t.identifier('meta')));
}
if (t.isCallExpression(path.parentPath.parent) && path.parent.property.name === 'glob') {
const node = path.parentPath.parent;
if (node.arguments.length === 0) {
throw new Error('import.meta.glob must have at least one argument');
}
const [pattern, options = {}] = node.arguments;
if (!t.isStringLiteral(pattern)) {
throw new Error('import.meta.glob first argument must be a string literal');
}
const opts = { cwd: require('path').dirname(state.filename) };
for (const prop of options.properties || []) opts[prop.key.name] = prop.value.value;
const files = glob.sync(pattern.value, opts);
const callExpression = path.find(p => p.isCallExpression());
if (opts.eager) {
const properties = [];
const buildImport = template('import * as %%local%% from %%source%%;', { sourceType: 'module' });
const statement = callExpression.getStatementParent();
for (let i = 0; i < files.length; i++) {
const file = files[i];
const local = `__glob_${globIndex++}_${i}`;
statement.insertBefore(buildImport({ local: t.identifier(local), source: t.stringLiteral(file) }));
properties.push(t.objectProperty(t.stringLiteral(file), t.identifier(local)));
}
callExpression.replaceWith(t.objectExpression(properties));
} else {
const properties = [];
const buildDynamicImport = template.expression('() => import(%%source%%)', { sourceType: 'module' });
for (let i = 0; i < files.length; i++) {
const file = files[i];
properties.push(t.objectProperty(t.stringLiteral(file), buildDynamicImport({ source: t.stringLiteral(file) })));
}
callExpression.replaceWith(t.objectExpression(properties));
}
}

@@ -65,0 +107,0 @@ },

@@ -13,8 +13,11 @@ 'use strict';

module.exports = class Cache {
constructor({ path: cachePath, identifier }) {
constructor({ path: cachePath, identifier, clean = false }) {
this.path = cachePath;
if (typeof identifier === 'function') this.identifier = identifier;
this.clean = clean;
}
identifier({ packet }) {
identifier(app) {
const { packet } = app;
const { uglifyOptions } = app;
const rPorterDir = new RegExp(path.resolve(__dirname, '..'), 'g');

@@ -28,2 +31,3 @@ const result = JSON.stringify({

},
uglifyOptions,
});

@@ -66,6 +70,6 @@ return result.replace(rPorterDir, '<porterDir>');

this.salt = this.identifier({ packet });
if (this.clean) await fs.rm(this.path, { recursive: true, force: true });
const saltPath = path.join(this.path, 'salt.cache');
const salt = await fs.readFile(saltPath, 'utf8').catch(() => '');
if (salt !== this.salt) {
if (!this.clean && salt !== this.salt) {
if (salt) debug('cache salt changed from %j to %j', salt, this.salt);

@@ -72,0 +76,0 @@ await fs.mkdir(path.dirname(saltPath), { recursive: true });

@@ -208,3 +208,3 @@ 'use strict';

const obj = {
babel: ['babel.config.js', '.babelrc'],
babel: ['babel.config.js', 'babel.config.cjs', '.babelrc'],
typescript: 'tsconfig.json',

@@ -211,0 +211,0 @@ };

@@ -63,3 +63,3 @@ 'use strict';

const cachePath = path.resolve(root, opts.cache && opts.cache.path || output.path);
const cache = new Cache({ path: cachePath });
const cache = new Cache({ ...opts.cache, path: cachePath });

@@ -208,3 +208,3 @@ const bundle = { exclude: [], ...opts.bundle };

await packet.prepare();
await cache.prepare({ packet });
await cache.prepare(this);

@@ -265,2 +265,3 @@ debug('parse preload, entries, and lazyload');

async compileAll({ entries = [] }) {
if (this.output.clean) await fs.rm(this.output.path, { recursive: true, force: true });
await this.ready({ minify: true });

@@ -267,0 +268,0 @@

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