@cara/porter
Advanced tools
Comparing version 4.3.4 to 4.4.0
@@ -31,9 +31,2 @@ /* eslint-env browser */ | ||
if (!Date.now) { | ||
Date.now = function() { | ||
return +new Date(); | ||
}; | ||
} | ||
var system = { lock: {}, registry: {}, entries: {}, preload: [] }; | ||
@@ -75,2 +68,3 @@ Object.assign(system, process.env.loaderConfig); | ||
var rWasm = /\.wasm$/; | ||
var rJson = /\.json$/; | ||
var rExt = /(\.\w+)$/; | ||
@@ -120,10 +114,6 @@ var rDigest = /\.[0-9a-f]{8}(\.\w+)$/; | ||
el.href = uri; | ||
el.onload = function() { | ||
callback(); | ||
}; | ||
el.onerror = function(cause) { | ||
var err = new Error('Failed to fetch ' + uri); | ||
err.cause = cause; | ||
onload(el, function(err) { | ||
el = el.onload = el.onerror = el.onreadystatechange = null; | ||
callback(err); | ||
}; | ||
}); | ||
head.insertBefore(el, baseElement); | ||
@@ -240,3 +230,3 @@ }; | ||
id = id.replace(/\.(?:jsx?|tsx?|mjs|cjs)$/, '.js').replace(/\.(?:less|sass|scss)$/, '.css'); | ||
return /\.(?:css|js|wasm)$/.test(id) ? id : id + '.js'; | ||
return /\.(?:css|js|json|wasm)$/.test(id) ? id : id + '.js'; | ||
} | ||
@@ -336,3 +326,9 @@ | ||
this.status = MODULE_INIT; | ||
this.meta = { url: baseUrl + id }; | ||
this.meta = { | ||
url: parseUri(baseUrl + id), | ||
resolve: function(specifier) { | ||
var result = Module.resolve(specifier, id); | ||
return result ? parseUri(baseUrl + result) : ''; | ||
}, | ||
}; | ||
registry[id] = this; | ||
@@ -361,2 +357,15 @@ } | ||
function importError(specifiers) { | ||
var message = 'import(' + JSON.stringify(specifiers) + ') timeout'; | ||
var pendingModules = []; | ||
for (var id in registry) { | ||
var mod = registry[id]; | ||
if (mod.status < MODULE_FETCHED) pendingModules.push(id); | ||
} | ||
if (pendingModules.length > 0) { | ||
message += ' for pending modules (' + JSON.stringify(pendingModules) + ')'; | ||
} | ||
return new Error(message); | ||
} | ||
Module.prototype.fetch = function() { | ||
@@ -376,9 +385,19 @@ var mod = this; | ||
var uri = parseUri(mod.id); | ||
if (fetching[uri]) return; | ||
fetching[uri] = true; | ||
request(uri, function(err) { | ||
if (err) mod.status = MODULE_ERROR; | ||
function onFetch(err) { | ||
if (err) { | ||
mod.status = MODULE_ERROR; | ||
if (!/^Failed to fetch/.test(err.message)) console.error(err); | ||
} | ||
if (mod.status < MODULE_FETCHED) mod.status = MODULE_FETCHED; | ||
mod.uri = uri; | ||
mod.ignite(); | ||
} | ||
if (fetching[uri]) { | ||
fetching[uri].push(onFetch); | ||
return; | ||
} | ||
fetching[uri] = [onFetch]; | ||
request(uri, function(err) { | ||
var callbacks = fetching[uri]; | ||
for (var j = 0; j < callbacks.length; j++) callbacks[j](err); | ||
fetching[uri] = null; | ||
@@ -458,5 +477,12 @@ }); | ||
// foo.d41d8cd9.css might exists | ||
require.async([ specifier, id.replace(rExt, '.css') ], resolve); | ||
const specifiers = [specifier]; | ||
const cssEntry = id.replace(rExt, '.css'); | ||
if (rDigest.test(parseUri(cssEntry))) specifiers.push(cssEntry); | ||
require.async(specifiers, function(exports) { | ||
if (exports.__esModule) return resolve(exports); | ||
if (rJson.test(id)) return resolve({ default: exports }); | ||
resolve(Object.assign({ default: exports }, exports)); | ||
}); | ||
setTimeout(function() { | ||
reject(new Error('import(' + JSON.stringify(specifier) + ') timeout')); | ||
reject(importError(specifier)); | ||
}, system.timeout); | ||
@@ -596,3 +622,3 @@ }), { __esModule: true }); | ||
entry.timeout = setTimeout(function() { | ||
throw new Error('Ignition timeout ' + specifiers.join(', ')); | ||
throw importError(specifiers); | ||
}, system.timeout); | ||
@@ -665,16 +691,2 @@ // Try ignite at the first place, which is necessary when the script is inline. | ||
/** | ||
* This works in IE 6-10 | ||
*/ | ||
if (!currentScript) { | ||
var scripts = document.getElementsByTagName('script'); | ||
for (var i = scripts.length - 1; i >= 0; i--) { | ||
var script = scripts[i]; | ||
if (script.readyState == 'interactive') { | ||
currentScript = script; | ||
break; | ||
} | ||
} | ||
} | ||
/** | ||
* This should only be necessary in IE 11 because it's the only browser that does | ||
@@ -681,0 +693,0 @@ * not support `document.currentScript`, or `script.readyState`. |
{ | ||
"name": "@cara/porter", | ||
"description": "A middleware for web modules", | ||
"version": "4.3.4", | ||
"version": "4.4.0", | ||
"main": "src/porter.js", | ||
@@ -45,4 +45,3 @@ "repository": { | ||
}, | ||
"license": "BSD-3-Clause", | ||
"gitHead": "bee0dc28299cc6c3b2cbea302972ab6a9ae94f52" | ||
"license": "BSD-3-Clause" | ||
} |
@@ -37,6 +37,6 @@ # Porter | ||
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: | ||
With the default setup, browser modules at `./components` folder is now accessible with `/path/to/file.js`. Take [examples/cli](https://github.com/porterhq/porter/tree/master/examples/cli) for example, the file structure shall resemble that of below: | ||
```bash | ||
➜ demo-cli git:(master) tree -L 2 | ||
➜ cli git:(master) tree -L 2 | ||
. | ||
@@ -84,3 +84,3 @@ ├── components # browser modules | ||
> ``` | ||
> | ||
> | ||
> Both formats are no longer recommended, please use `<script src="/app.js?main"></script>` directly. | ||
@@ -101,3 +101,3 @@ | ||
// will bundle and fetch worker.js separately | ||
// will bundle and fetch worker.js separately | ||
import Worker from 'worker-loader!./worker.js'; | ||
@@ -124,6 +124,6 @@ ``` | ||
root: process.cwd(), | ||
// paths of browser modules, or components, defaults to `'components'` | ||
paths: 'components', | ||
// output settings | ||
@@ -134,3 +134,3 @@ output: { | ||
}, | ||
// cache settings | ||
@@ -140,3 +140,3 @@ cache: { | ||
path: '.porter-cache', | ||
// cache identifier to shortcut cache invalidation | ||
@@ -152,6 +152,6 @@ identifier({ packet }) { | ||
}, | ||
// preload common dependencies, defaults to `[]` | ||
preload: [ 'preload', '@babel/runtime' ], | ||
// the module resolution behaviour | ||
@@ -164,15 +164,15 @@ resolve: { | ||
}, | ||
// 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: '', | ||
{ libraryName: 'lodash', | ||
libraryDirectory: '', | ||
camel2DashComponentName: false }, | ||
], | ||
}, | ||
// transpile settings | ||
@@ -183,3 +183,3 @@ transpile: { | ||
}, | ||
// bundle settings | ||
@@ -190,3 +190,3 @@ bundle: { | ||
}, | ||
// source settings | ||
@@ -196,3 +196,3 @@ source: { | ||
serve: process.env.NODE_ENV !== 'production', | ||
// the `sourceRoot` in the generated source map, defaults to `'/'` | ||
@@ -199,0 +199,0 @@ root: 'localhost:3000', |
@@ -40,3 +40,3 @@ # Porter | ||
```bash | ||
➜ demo-cli git:(master) tree -L 2 | ||
➜ examples/cli git:(master) tree -L 2 | ||
. | ||
@@ -84,3 +84,3 @@ ├── components # browser modules | ||
> ``` | ||
> | ||
> | ||
> 上述写法都不再推荐,直接写 `<script src="/app.js?main"></script>` 就好。 | ||
@@ -101,3 +101,3 @@ | ||
// will bundle and fetch worker.js separately | ||
// will bundle and fetch worker.js separately | ||
import Worker from 'worker-loader!./worker.js'; | ||
@@ -126,6 +126,6 @@ ``` | ||
root: process.cwd(), | ||
// paths of browser modules, or components, defaults to `'components'` | ||
paths: 'components', | ||
// output settings | ||
@@ -136,3 +136,3 @@ output: { | ||
}, | ||
// cache settings | ||
@@ -142,3 +142,3 @@ cache: { | ||
path: '.porter-cache', | ||
// cache identifier to shortcut cache invalidation | ||
@@ -154,6 +154,6 @@ identifier({ packet }) { | ||
}, | ||
// preload common dependencies, defaults to `[]` | ||
preload: [ 'preload', '@babel/runtime' ], | ||
// the module resolution behaviour | ||
@@ -166,15 +166,15 @@ resolve: { | ||
}, | ||
// 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: '', | ||
{ libraryName: 'lodash', | ||
libraryDirectory: '', | ||
camel2DashComponentName: false }, | ||
], | ||
}, | ||
// transpile settings | ||
@@ -185,3 +185,3 @@ transpile: { | ||
}, | ||
// bundle settings | ||
@@ -192,3 +192,3 @@ bundle: { | ||
}, | ||
// source settings | ||
@@ -198,3 +198,3 @@ source: { | ||
serve: process.env.NODE_ENV !== 'production', | ||
// the `sourceRoot` in the generated source map, defaults to `'/'` | ||
@@ -201,0 +201,0 @@ root: 'localhost:3000', |
@@ -67,5 +67,2 @@ 'use strict'; | ||
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') { | ||
@@ -104,2 +101,4 @@ const node = path.parentPath.parent; | ||
} | ||
} else { | ||
path.replaceWith(t.memberExpression(t.identifier('__module'), t.identifier('meta'))); | ||
} | ||
@@ -106,0 +105,0 @@ }, |
@@ -402,3 +402,5 @@ 'use strict'; | ||
const subnode = await this.createSourceNode({ | ||
source: `porter:///${path.relative(app.root, mod.fpath)}`, | ||
// relative path might start with ../../ if dependencies were found at workspace root | ||
// ../../node_modules/react/index.js => node_modules/react/index.js | ||
source: `porter:///${path.relative(app.root, mod.fpath).replace(/^(\.\.\/)+/, '')}`, | ||
sourceContent: mod.code || await fs.readFile(mod.fpath, 'utf-8'), | ||
@@ -405,0 +407,0 @@ code, |
@@ -18,3 +18,3 @@ 'use strict'; | ||
const { packet } = this; | ||
const { imports, dynamicImports } = matchRequire.findAll(code); | ||
const { imports, dynamicImports, __esModule } = matchRequire.findAll(code); | ||
function ignoreImport(specifier) { | ||
@@ -25,2 +25,3 @@ return packet.browser[specifier] !== false && specifier !== 'heredoc'; | ||
this.dynamicImports = dynamicImports.filter(ignoreImport); | ||
if (this.__esModule == null) this.__esModule = __esModule; | ||
} | ||
@@ -73,11 +74,18 @@ | ||
const { app } = this; | ||
const { app, packet } = this; | ||
const { code } = await this.load(); | ||
this.cache = await app.cache.get(this.id, code); | ||
if (!this.imports && this.cache) { | ||
this.imports = this.cache.imports; | ||
this.dynamicImports = this.cache.dynamicImports; | ||
this.__esModule = this.cache.__esModule; | ||
} | ||
if (!this.imports) this.matchImport(code); | ||
if (this.__esModule && !packet.transpiler) { | ||
packet.transpiler = app.packet.transpiler; | ||
packet.transpilerOpts = app.packet.transpilerOpts; | ||
} | ||
const [ children, dynamicChildren ] = await Promise.all([ | ||
@@ -84,0 +92,0 @@ Promise.all(this.imports.map(this.parseImport, this)), |
@@ -10,2 +10,16 @@ 'use strict'; | ||
function decodeUnicodeLiteral(text) { | ||
if (typeof text !== 'string') return text; | ||
try { | ||
return text.replace(/(\\u[0-9a-f]{4})/ig, (m, literal) => { | ||
return String.fromCodePoint(parseInt(literal.slice(2), 16)); | ||
}); | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
return text; | ||
}; | ||
exports.decodeUnicodeLiteral = decodeUnicodeLiteral; | ||
/** | ||
@@ -198,2 +212,3 @@ * Finds all of the dependencies `require`d or `import`ed in the code passed in. | ||
let __esModule = false; | ||
next(); | ||
@@ -212,2 +227,3 @@ while (part) { | ||
findImport(); | ||
__esModule = true; | ||
} | ||
@@ -217,2 +233,3 @@ else if (part == 'export') { | ||
if (nextPart === '{' || nextPart === '*') findExportFrom(); | ||
__esModule = true; | ||
} | ||
@@ -222,3 +239,6 @@ next(); | ||
return { imports, dynamicImports }; | ||
return { | ||
imports: imports.map(decodeUnicodeLiteral), | ||
dynamicImports: dynamicImports.map(decodeUnicodeLiteral), | ||
__esModule }; | ||
}; |
@@ -141,3 +141,3 @@ 'use strict'; | ||
if (!children.includes(depBundle)) children.push(depBundle); | ||
} | ||
} | ||
} | ||
@@ -150,3 +150,3 @@ | ||
copy.manifest = manifest; | ||
manifest[child.outkey.replace(/\.\w+$/, child.format)] = child.output; | ||
manifest[child.outkey.replace(/\.(?!json)\w+$/, child.format)] = child.output; | ||
} | ||
@@ -163,5 +163,5 @@ } else if (this.fake) { | ||
setCache(source, result) { | ||
const { app, imports, dynamicImports } = this; | ||
const { app, imports, dynamicImports, __esModule } = this; | ||
if (typeof result.map === 'string') result.map = JSON.parse(result.map); | ||
const cache = { ...result, imports, dynamicImports }; | ||
const cache = { ...result, imports, dynamicImports, __esModule }; | ||
app.cache.set(this.id, source, cache).catch(err => console.error(err)); | ||
@@ -268,3 +268,3 @@ this.cache = cache; | ||
* @param {string} opts.code | ||
* @returns {Array} | ||
* @returns {Promise<Array>} | ||
*/ | ||
@@ -287,3 +287,3 @@ async checkImports({ code }) { | ||
const { code, map } = await this.load(); | ||
this.matchImport(code); | ||
if (!this.imports) this.matchImport(code); | ||
this.setCache(code, await this.transpile({ code, map })); | ||
@@ -290,0 +290,0 @@ } |
@@ -193,4 +193,5 @@ 'use strict'; | ||
async parseDepPaths() { | ||
const { depPaths } = this; | ||
const { app, depPaths } = this; | ||
let packet = this; | ||
let parentDir; | ||
@@ -202,4 +203,15 @@ while (packet) { | ||
} | ||
parentDir = path.join(packet.dir, '..'); | ||
packet = packet.parent; | ||
} | ||
let count = 0; | ||
// add global node_modules at root workspace | ||
while (app.packet.name.startsWith('@cara/') && parentDir && ++count <= 2) { | ||
const depPath = path.join(parentDir, 'node_modules'); | ||
if (existsSync(depPath) && !depPaths.includes(depPath)) { | ||
depPaths.push(depPath); | ||
} | ||
parentDir = path.join(parentDir, '..'); | ||
} | ||
} | ||
@@ -466,2 +478,3 @@ | ||
const dir = path.join(depPath, name); | ||
if (existsSync(dir)) { | ||
@@ -468,0 +481,0 @@ const { app } = this; |
@@ -5,3 +5,3 @@ 'use strict'; | ||
const debug = require('debug')('porter'); | ||
const { existsSync, promises: fs } = require('fs'); | ||
const fs = require('fs/promises'); | ||
const mime = require('mime'); | ||
@@ -62,3 +62,3 @@ const path = require('path'); | ||
const transpile = { include: [], ...opts.transpile }; | ||
const transpile = { include: [], typescript: 'tsc', ...opts.transpile }; | ||
const cachePath = path.resolve(root, opts.cache && opts.cache.path || output.path); | ||
@@ -128,7 +128,13 @@ const cache = new Cache({ ...opts.cache, path: cachePath }); | ||
readFilePath(fpath) { | ||
return Promise.all([ | ||
readFile(fpath), | ||
lstat(fpath).then(stats => ({ 'Last-Modified': stats.mtime.toJSON() })) | ||
]); | ||
async readFilePath(fpath) { | ||
if (!fpath) return null; | ||
try { | ||
return await Promise.all([ | ||
readFile(fpath), | ||
lstat(fpath).then(stats => ({ 'Last-Modified': stats.mtime.toJSON() })) | ||
]); | ||
} catch (err) { | ||
if (err.code === 'ENOENT') return null; | ||
throw err; | ||
} | ||
} | ||
@@ -310,31 +316,22 @@ | ||
async isRawFile(file) { | ||
if (!this.source.serve) return false; | ||
async readRawFile(file) { | ||
let fpath; | ||
if (file.startsWith('node_modules')) { | ||
// cnpm/npminstall rename packages to folders like _@babel_core@7.16.10@@babel/core | ||
const [, name] = file.replace(/^node_modules\//, '').replace(/^_@?[^@]+@[^@]+@/, '').match(rModuleId); | ||
// #1 cannot require('mocha') just yet | ||
return this.packet.find({ name }) || name == 'mocha'; | ||
const [, name, , entry] = file.replace(/^node_modules\//, '').replace(/^_@?[^@]+@[^@]+@/, '').match(rModuleId); | ||
const packet = this.packet.find({ name }); | ||
fpath = packet && path.join(packet.dir, entry); | ||
} else { | ||
fpath = path.join(this.root, file); | ||
let found; | ||
for (const dir of this.packet.paths) { | ||
if (fpath.startsWith(dir)) found = true; | ||
} | ||
if (!found) fpath = null; | ||
} | ||
// FIXME: packages/demo-component has package paths set to `.` which makes source serving error prone because the pathnames of source and the output are the same. | ||
if (this.packet.paths.includes(this.root)) return false; | ||
const fpath = path.join(this.root, file); | ||
for (const dir of this.packet.paths) { | ||
if (fpath.startsWith(dir) && existsSync(fpath)) return true; | ||
} | ||
return false; | ||
return await this.readFilePath(fpath); | ||
} | ||
async readRawFile(file) { | ||
const fpath = path.join(this.root, file); | ||
if (existsSync(fpath)) { | ||
return this.readFilePath(fpath); | ||
} | ||
} | ||
async parseId(id, options) { | ||
@@ -436,2 +433,3 @@ const { parseCache } = this; | ||
const mod = await packet.parseFile(file); | ||
if (!mod) return; | ||
const { code } = await mod.obtain(); | ||
@@ -443,2 +441,3 @@ const mtime = (await lstat(mod.fpath)).mtime.toJSON(); | ||
async readFile(file, query) { | ||
file = decodeURIComponent(file); | ||
await this.ready({ minify: process.env.NODE_ENV === 'production' }); | ||
@@ -460,5 +459,2 @@ | ||
} | ||
else if (await this.isRawFile(file)) { | ||
result = await this.readRawFile(file); | ||
} | ||
else if (ext === '.js') { | ||
@@ -478,7 +474,9 @@ result = await this.readJs(file, query); | ||
const [fpath] = await packet.resolve(file); | ||
if (fpath) { | ||
result = await this.readFilePath(fpath); | ||
} | ||
result = await this.readFilePath(fpath); | ||
} | ||
if (!result && this.source.serve) { | ||
result = await this.readRawFile(file); | ||
} | ||
if (result) { | ||
@@ -485,0 +483,0 @@ const body = result[0]; |
'use strict'; | ||
const path = require('path'); | ||
// const fs = require('fs/promises'); | ||
const JsModule = require('./js_module'); | ||
@@ -10,33 +11,45 @@ | ||
const { code } = await super.load(); | ||
const ts = packet.tryRequire('typescript'); | ||
const { imports: oldImports } = this; | ||
if (!ts) return { code }; | ||
this.matchImport(code); | ||
const { dynamicImports, imports } = this; | ||
const ts = packet.tryRequire('typescript'); | ||
const compilerOptions = ts && { | ||
target: ts.ScriptTarget.ES2022, | ||
sourceMap: false, | ||
}; | ||
// remove imports of type definitions in advance, such as | ||
// import { IModel } from './foo.d.ts'; | ||
// import { IOptions } from './bar.ts'; | ||
const result = await this._transpile({ code }, { | ||
target: ts.ScriptTarget.ES2022, | ||
sourceMap: false, | ||
}); | ||
const result = await this._transpile({ code }, compilerOptions); | ||
this.matchImport(result.code); | ||
// remove imports that are transformed from dynamic imports, such as | ||
// import('./utils/math') | ||
this.matchImport(result.code); | ||
// import('./utils/math'); | ||
for (let i = this.imports.length - 1; i >= 0; i--) { | ||
const specifier = this.imports[i]; | ||
if (dynamicImports.includes(specifier) || (oldImports && !oldImports.includes(specifier))) { | ||
this.imports.splice(i, 1); | ||
} | ||
} | ||
// restore css imports that might be removed when compiling with babel, such as | ||
// import './foo.css'; | ||
for (const specifier of imports) { | ||
if (specifier.endsWith('.css') && !this.imports.includes(specifier)) { | ||
this.imports.push(specifier); | ||
} | ||
} | ||
this.dynamicImports = dynamicImports; | ||
return result; | ||
} | ||
matchImport(code) { | ||
const { dynamicImports = [] } = this; | ||
super.matchImport(code); | ||
if (dynamicImports.length > 0) { | ||
this.dynamicImports = dynamicImports; | ||
this.imports = this.imports.filter(specifier => { | ||
return !dynamicImports.includes(specifier); | ||
}); | ||
async _transpile({ code }, compilerOptions) { | ||
const { app, fpath, packet } = this; | ||
// might transpile typescript with tools like babel or swc | ||
if (app.transpile.typescript !== 'tsc') { | ||
return super._transpile({ code }); | ||
} | ||
} | ||
async _transpile({ code }, compilerOptions) { | ||
const { fpath, packet } = this; | ||
const ts = packet.tryRequire('typescript'); | ||
@@ -43,0 +56,0 @@ |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
141842
3612