diff --git a/package.json b/package.json index af68e5df..e336eb36 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@google-cloud/firestore": "^0.19.0", "@sentry/node": "^4.3.0", "@tensorflow/tfjs-node": "^0.3.0", - "@zeit/webpack-asset-relocator-loader": "0.5.0-beta.6", + "@zeit/webpack-asset-relocator-loader": "0.5.1", "analytics-node": "^3.3.0", "apollo-server-express": "^2.2.2", "arg": "^4.1.0", diff --git a/readme.md b/readme.md index 32f55f3d..7ee0fd1d 100644 --- a/readme.md +++ b/readme.md @@ -36,6 +36,8 @@ $ ncc build input.js -o dist Outputs the Node.js compact build of `input.js` into `dist/index.js`. +It is also possible to build multiple files in a code-splitting build by passing additional inputs. + ### Execution Testing For testing and debugging, a file can be built into a temporary directory and executed with full source maps support with the command: @@ -82,20 +84,36 @@ require('@zeit/ncc')('/path/to/input', { v8cache: false, // default quiet: false, // default debugLog = false // default -}).then(({ code, map, assets }) => { - console.log(code); - // Assets is an object of asset file names to { source, permissions, symlinks } - // expected relative to the output code (if any) +}).then(({ files, symlinks }) => { + // files: an object of file names to { source, permissions } + // symlinks: an object of symlink mappings required for the build + // The main file is located at 'index.js': + files['index.js'].source }) ``` +Multiple entry points can be built by providing an object to build. In this case, those files are avaiable as additional output files: + +```js +require('@zeit/ncc')({ + entry1: '/path/to/input1', + entry2: '/path/to/input2 +}).then(({ files, symlinks }) => { + // named entry points are available at their names: + files['entry1.js'].source + files['entry1.js.map'].source + files['entry2.js'].source + // ... assets +}); +``` + When `watch: true` is set, the build object is not a promise, but has the following signature: ```js { // handler re-run on each build completion // watch errors are reported on "err" - handler (({ err, code, map, assets }) => { ... }) + handler (({ err, files, symlinks }) => { ... }) // handler re-run on each rebuild start rebuild (() => {}) // close the watcher diff --git a/scripts/build.js b/scripts/build.js index 22604fcf..20918210 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -6,64 +6,62 @@ const copy = promisify(require("copy")); const glob = promisify(require("glob")); const bytes = require("bytes"); +function assetList (files) { + return Object.keys(files).filter(file => file.startsWith('assets/')).map(file => file.substr(7)); +} + async function main() { for (const file of await glob(__dirname + "/../dist/**/*.@(js|cache|ts)")) { unlinkSync(file); } - const { code: cli, assets: cliAssets } = await ncc( - __dirname + "/../src/cli", + const { files: cliFiles } = await ncc( + { 'cli': __dirname + "/../src/cli" }, { - filename: "cli.js", externals: ["./index.js"], minify: true, v8cache: true } ); - checkUnknownAssets('cli', Object.keys(cliAssets)); + checkUnknownAssets('cli', assetList(cliFiles)); - const { code: index, assets: indexAssets } = await ncc( - __dirname + "/../src/index", + const { files: indexFiles } = await ncc( + { 'index': __dirname + "/../src/index" }, { // we dont care about watching, so we don't want // to bundle it. even if we did want watching and a bigger // bundle, webpack (and therefore ncc) cannot currently bundle // chokidar, which is quite convenient externals: ["chokidar"], - filename: "index.js", minify: true, v8cache: true } ); - checkUnknownAssets('index', Object.keys(indexAssets).filter(asset => !asset.startsWith('locales/') && asset !== 'worker.js' && asset !== 'index1.js')); + checkUnknownAssets('index', assetList(indexFiles).filter(asset => !asset.startsWith('locales/') && asset !== 'worker.js' && asset !== 'index.js')); - const { code: relocateLoader, assets: relocateLoaderAssets } = await ncc( - __dirname + "/../src/loaders/relocate-loader", - { filename: "relocate-loader.js", minify: true, v8cache: true } + const { files: relocateLoaderFiles } = await ncc( + { 'relocate-loader': __dirname + "/../src/loaders/relocate-loader",}, + { minify: true, v8cache: true } ); - checkUnknownAssets('relocate-loader', Object.keys(relocateLoaderAssets)); + checkUnknownAssets('relocate-loader', assetList(relocateLoaderFiles)); - const { code: shebangLoader, assets: shebangLoaderAssets } = await ncc( - __dirname + "/../src/loaders/shebang-loader", - { filename: "shebang-loader.js", minify: true, v8cache: true } + const { files: shebangLoaderFiles } = await ncc( + { 'shebang-loader': __dirname + "/../src/loaders/shebang-loader" }, + { minify: true, v8cache: true } ); - checkUnknownAssets('shebang-loader', Object.keys(shebangLoaderAssets)); + checkUnknownAssets('shebang-loader', assetList(shebangLoaderFiles)); - const { code: tsLoader, assets: tsLoaderAssets } = await ncc( - __dirname + "/../src/loaders/ts-loader", - { - filename: "ts-loader.js", - minify: true, - v8cache: true - } + const { files: tsLoaderFiles } = await ncc( + { 'ts-loader': __dirname + "/../src/loaders/ts-loader" }, + { minify: true, v8cache: true } ); - checkUnknownAssets('ts-loader', Object.keys(tsLoaderAssets).filter(asset => !asset.startsWith('lib/') && !asset.startsWith('typescript/lib'))); + checkUnknownAssets('ts-loader', assetList(tsLoaderFiles).filter(asset => !asset.startsWith('lib/') && !asset.startsWith('typescript/lib'))); - const { code: sourcemapSupport, assets: sourcemapAssets } = await ncc( - require.resolve("source-map-support/register"), - { filename: "sourcemap-register.js", minfiy: true, v8cache: true } + const { files: sourceMapSupportFiles } = await ncc( + { 'sourcemap-register': require.resolve("source-map-support/register") }, + { minifiy: true, v8cache: true } ); - checkUnknownAssets('source-map-support/register', Object.keys(sourcemapAssets)); + checkUnknownAssets('source-map-support/register', assetList(sourceMapSupportFiles)); // detect unexpected asset emissions from core build function checkUnknownAssets (buildName, assets) { @@ -73,22 +71,22 @@ async function main() { console.log(assets); } - writeFileSync(__dirname + "/../dist/ncc/cli.js.cache", cliAssets["cli.js.cache"].source); - writeFileSync(__dirname + "/../dist/ncc/index.js.cache", indexAssets["index.js.cache"].source); - writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache", sourcemapAssets["sourcemap-register.js.cache"].source); - writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache", relocateLoaderAssets["relocate-loader.js.cache"].source); - writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache", shebangLoaderAssets["shebang-loader.js.cache"].source); - writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache", tsLoaderAssets["ts-loader.js.cache"].source); - - writeFileSync(__dirname + "/../dist/ncc/cli.js.cache.js", cliAssets["cli.js.cache.js"].source); - writeFileSync(__dirname + "/../dist/ncc/index.js.cache.js", indexAssets["index.js.cache.js"].source); - writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache.js", sourcemapAssets["sourcemap-register.js.cache.js"].source); - writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache.js", relocateLoaderAssets["relocate-loader.js.cache.js"].source); - writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache.js", shebangLoaderAssets["shebang-loader.js.cache.js"].source); - writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache.js", tsLoaderAssets["ts-loader.js.cache.js"].source); - - writeFileSync(__dirname + "/../dist/ncc/cli.js", cli, { mode: 0o777 }); - writeFileSync(__dirname + "/../dist/ncc/index.js", index); + writeFileSync(__dirname + "/../dist/ncc/cli.js.cache", cliFiles["cli.js.cache"].source); + writeFileSync(__dirname + "/../dist/ncc/index.js.cache", indexFiles["index.js.cache"].source); + writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache", sourceMapSupportFiles["sourcemap-register.js.cache"].source); + writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache", relocateLoaderFiles["relocate-loader.js.cache"].source); + writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache", shebangLoaderFiles["shebang-loader.js.cache"].source); + writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache", tsLoaderFiles["ts-loader.js.cache"].source); + + writeFileSync(__dirname + "/../dist/ncc/cli.js.cache.js", cliFiles["cli.js.cache.js"].source); + writeFileSync(__dirname + "/../dist/ncc/index.js.cache.js", indexFiles["index.js.cache.js"].source); + writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache.js", sourceMapSupportFiles["sourcemap-register.js.cache.js"].source); + writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache.js", relocateLoaderFiles["relocate-loader.js.cache.js"].source); + writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache.js", shebangLoaderFiles["shebang-loader.js.cache.js"].source); + writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache.js", tsLoaderFiles["ts-loader.js.cache.js"].source); + + writeFileSync(__dirname + "/../dist/ncc/cli.js", cliFiles["cli.js"].source, { mode: 0o777 }); + writeFileSync(__dirname + "/../dist/ncc/index.js", indexFiles["index.js"].source); writeFileSync(__dirname + "/../dist/ncc/typescript.js", ` const { Module } = require('module'); const m = new Module('', null); @@ -104,10 +102,10 @@ catch (e) { } module.exports = typescript; `); - writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js", sourcemapSupport); - writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js", relocateLoader); - writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js", shebangLoader); - writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js", tsLoader); + writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js", sourceMapSupportFiles["sourcemap-register.js"].source); + writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js", relocateLoaderFiles["relocate-loader.js"].source); + writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js", shebangLoaderFiles["shebang-loader.js"].source); + writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js", tsLoaderFiles["ts-loader.js"].source); writeFileSync(__dirname + "/../dist/ncc/loaders/uncacheable.js", readFileSync(__dirname + "/../src/loaders/uncacheable.js")); writeFileSync(__dirname + "/../dist/ncc/loaders/empty-loader.js", readFileSync(__dirname + "/../src/loaders/empty-loader.js")); writeFileSync(__dirname + "/../dist/ncc/loaders/notfound-loader.js", readFileSync(__dirname + "/../src/loaders/notfound-loader.js")); diff --git a/src/cli.js b/src/cli.js index 49d6df9e..f52cad4d 100755 --- a/src/cli.js +++ b/src/cli.js @@ -2,7 +2,6 @@ const { resolve, relative, dirname, sep } = require("path"); const glob = require("glob"); -const shebangRegEx = require("./utils/shebang"); const rimraf = require("rimraf"); const crypto = require("crypto"); const { writeFileSync, unlink, existsSync, symlinkSync } = require("fs"); @@ -12,8 +11,8 @@ const { version: nccVersion } = require('../package.json'); const usage = `Usage: ncc Commands: - build [opts] - run [opts] + build + [opts] + run ? [opts] cache clean|dir|size help version @@ -49,58 +48,39 @@ else { api = true; } -function renderSummary(code, map, assets, outDir, buildTime) { +function renderSummary(files, outDir, buildTime) { if (outDir && !outDir.endsWith(sep)) outDir += sep; - const codeSize = Math.round(Buffer.byteLength(code, "utf8") / 1024); - const mapSize = map ? Math.round(Buffer.byteLength(map, "utf8") / 1024) : 0; - const assetSizes = Object.create(null); - let totalSize = codeSize; - let maxAssetNameLength = 8 + (map ? 4 : 0); // length of index.js(.map)? - for (const asset of Object.keys(assets)) { - const assetSource = assets[asset].source; + const fileSizes = Object.create(null); + let totalSize = 0; + let maxAssetNameLength = 0; + for (const file of Object.keys(files)) { + const assetSource = files[file].source; + if (!assetSource) continue; const assetSize = Math.round( (assetSource.byteLength || Buffer.byteLength(assetSource, "utf8")) / 1024 ); - assetSizes[asset] = assetSize; + fileSizes[file] = assetSize; totalSize += assetSize; - if (asset.length > maxAssetNameLength) maxAssetNameLength = asset.length; + if (file.length > maxAssetNameLength) maxAssetNameLength = file.length; } - const orderedAssets = Object.keys(assets).sort((a, b) => - assetSizes[a] > assetSizes[b] ? 1 : -1 - ); + const orderedAssets = Object.keys(files).filter(file => typeof files[file] === 'object').sort((a, b) => { + if ((a.startsWith('asset/') || b.startsWith('asset/')) && + !(a.startsWith('asset/') && b.startsWith('asset/'))) + return a.startsWith('asset/') ? 1 : -1; + return fileSizes[a] > fileSizes[b] ? 1 : -1; + }); const sizePadding = totalSize.toString().length; - let indexRender = `${codeSize - .toString() - .padStart(sizePadding, " ")}kB ${outDir}${"index.js"}`; - let indexMapRender = map ? `${mapSize - .toString() - .padStart(sizePadding, " ")}kB ${outDir}${"index.js.map"}` : ''; - let output = "", first = true; - for (const asset of orderedAssets) { + for (const file of orderedAssets) { if (first) first = false; else output += "\n"; - if (codeSize < assetSizes[asset] && indexRender) { - output += indexRender + "\n"; - indexRender = null; - } - if (mapSize && mapSize < assetSizes[asset] && indexMapRender) { - output += indexMapRender + "\n"; - indexMapRender = null; - } - output += `${assetSizes[asset] + output += `${fileSizes[file] .toString() - .padStart(sizePadding, " ")}kB ${outDir}${asset}`; - } - - if (indexRender) { - output += (first ? "" : "\n") + indexRender; - first = false; + .padStart(sizePadding, " ")}kB ${outDir}${file}`; } - if (indexMapRender) output += (first ? "" : "\n") + indexMapRender; output += `\n${totalSize}kB [${buildTime}ms] - ncc ${nccVersion}`; @@ -187,8 +167,7 @@ async function runCmd (argv, stdout, stderr) { break; case "run": - if (args._.length > 2) - errTooManyArguments("run"); + var runArgs = args._.slice(2); if (args["--out"]) errFlagNotCompatible("--out", "run"); @@ -206,14 +185,12 @@ async function runCmd (argv, stdout, stderr) { // fallthrough case "build": - if (args._.length > 2) - errTooManyArguments("build"); + const buildFiles = runArgs ? resolve(args._[1]) : args._.slice(1); let startTime = Date.now(); let ps; - const buildFile = eval("require.resolve")(resolve(args._[1] || ".")); const ncc = require("./index.js")( - buildFile, + buildFiles, { debugLog: args["--debug"], minify: args["--minify"], @@ -227,7 +204,7 @@ async function runCmd (argv, stdout, stderr) { } ); - async function handler ({ err, code, map, assets, symlinks }) { + async function handler ({ err, files, symlinks }) { // handle watch errors if (err) { stderr.write(err + '\n'); @@ -245,26 +222,25 @@ async function runCmd (argv, stdout, stderr) { new Promise((resolve, reject) => unlink(file, err => err ? reject(err) : resolve()) )) ); - writeFileSync(outDir + "/index.js", code, { mode: code.match(shebangRegEx) ? 0o777 : 0o666 }); - if (map) writeFileSync(outDir + "/index.js.map", map); - for (const asset of Object.keys(assets)) { - const assetPath = outDir + "/" + asset; - mkdirp.sync(dirname(assetPath)); - writeFileSync(assetPath, assets[asset].source, { mode: assets[asset].permissions }); + for (const filename of Object.keys(files)) { + const file = files[filename]; + const filePath = outDir + "/" + filename; + mkdirp.sync(dirname(filePath)); + writeFileSync(filePath, file.source, { mode: file.permissions }); } - for (const symlink of Object.keys(symlinks)) { - const symlinkPath = outDir + "/" + symlink; - symlinkSync(symlinks[symlink], symlinkPath); + for (const filename of Object.keys(symlinks)) { + const file = symlinks[filename]; + const filePath = outDir + "/" + filename; + mkdirp.sync(dirname(filePath)); + symlinkSync(file, filePath); } if (!quiet) { - stdout.write( + stdout.write( renderSummary( - code, - map, - assets, + files, run ? "" : relative(process.cwd(), outDir), Date.now() - startTime, ) + '\n' @@ -277,6 +253,7 @@ async function runCmd (argv, stdout, stderr) { if (run) { // find node_modules const root = resolve('/node_modules'); + const buildFile = resolve(args._[1]); let nodeModulesDir = dirname(buildFile) + "/node_modules"; do { if (nodeModulesDir === root) { @@ -288,7 +265,7 @@ async function runCmd (argv, stdout, stderr) { } while (nodeModulesDir = resolve(nodeModulesDir, "../../node_modules")); if (nodeModulesDir) symlinkSync(nodeModulesDir, outDir + "/node_modules", "junction"); - ps = require("child_process").fork(outDir + "/index.js", { + ps = require("child_process").fork(outDir + "/index.js", runArgs, { stdio: api ? 'pipe' : 'inherit' }); if (api) { diff --git a/src/index.js b/src/index.js index 4ee289bd..d0e5acdc 100644 --- a/src/index.js +++ b/src/index.js @@ -1,14 +1,13 @@ const resolve = require("resolve"); const fs = require("graceful-fs"); const crypto = require("crypto"); -const { sep, join, dirname } = require("path"); +const { join, dirname, resolve: pathResolve, basename, extname } = require("path"); const webpack = require("webpack"); const MemoryFS = require("memory-fs"); const terser = require("terser"); const tsconfigPaths = require("tsconfig-paths"); const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); const shebangRegEx = require('./utils/shebang'); -const { pkgNameRegEx } = require("./utils/get-package-base"); const nccCacheDir = require("./utils/ncc-cache-dir"); const { version: nccVersion } = require('../package.json'); @@ -36,7 +35,6 @@ module.exports = ( { cache, externals = [], - filename = "index.js", minify = false, sourceMap = false, sourceMapRegister = true, @@ -47,24 +45,31 @@ module.exports = ( debugLog = false } = {} ) => { + const processedEntry = Object.create(null); + if (entry instanceof Array) { + entry.forEach(file => { + processedEntry[basename(file)] = pathResolve(file); + }); + } + else if (typeof entry === 'object') { + Object.keys(entry).forEach(item => { + if (!item.endsWith('.js') && !item.endsWith('.mjs')) + processedEntry[item + '.js'] = pathResolve(entry[item]); + else + processedEntry[item] = pathResolve(entry[item]); + }); + } + else if (typeof entry === 'string') { + processedEntry['index.js'] = pathResolve(entry); + } if (!quiet) { console.log(`ncc: Version ${nccVersion}`); - console.log(`ncc: Compiling file ${filename}`); + console.log(`ncc: Compiling file${Object.keys(processedEntry).length > 1 ? 's' : ''} ${Object.values(processedEntry).join(', ')}`); } - const resolvedEntry = resolve.sync(entry); - process.env.TYPESCRIPT_LOOKUP_PATH = resolvedEntry; - const shebangMatch = fs.readFileSync(resolvedEntry).toString().match(shebangRegEx); + // How to handle for multi-entry case? + process.env.TYPESCRIPT_LOOKUP_PATH = Object.values(processedEntry)[0]; const mfs = new MemoryFS(); - const existingAssetNames = [filename]; - if (sourceMap) { - existingAssetNames.push(`${filename}.map`); - existingAssetNames.push('sourcemap-register.js'); - } - if (v8cache) { - existingAssetNames.push(`${filename}.cache`); - existingAssetNames.push(`${filename}.cache.js`); - } const resolvePlugins = []; // add TsconfigPathsPlugin to support `paths` resolution in tsconfig // we need to catch here because the plugin will @@ -103,11 +108,11 @@ module.exports = ( let watcher, watchHandler, rebuildHandler; const compiler = webpack({ - entry, + entry: processedEntry, cache: cache === false ? undefined : { type: "filesystem", cacheDirectory: typeof cache === 'string' ? cache : nccCacheDir, - name: `ncc_${hashOf(entry)}`, + name: `ncc_${hashOf(JSON.stringify(entry))}`, version: nccVersion }, amd: false, @@ -116,14 +121,17 @@ module.exports = ( minimize: false, moduleIds: 'deterministic', chunkIds: 'deterministic', - mangleExports: false + mangleExports: false, + splitChunks: typeof entry === 'object' && Object.keys(entry).length > 1 ? { + chunks: 'all' + } : false }, devtool: sourceMap ? "source-map" : false, mode: "production", target: "node", output: { path: "/", - filename, + filename: "[name]", libraryTarget: "commonjs2" }, resolve: { @@ -154,7 +162,7 @@ module.exports = ( }, { loader: eval('__dirname + "/loaders/relocate-loader.js"'), options: { - existingAssetNames, + outputAssetBase: 'assets', escapeNonAnalyzableRequires: true, wrapperCompatibility: true, debugLog @@ -260,7 +268,7 @@ module.exports = ( const errLog = stats.compilation.errors.map(err => err.message).join('\n'); return reject(new Error(errLog)); } - resolve(); + resolve(stats); }); }); }) @@ -279,7 +287,7 @@ module.exports = ( return watchHandler({ err }); if (stats.hasErrors()) return watchHandler({ err: stats.toString() }); - const returnValue = finalizeHandler(); + const returnValue = finalizeHandler(stats); if (watchHandler) watchHandler(returnValue); else @@ -312,88 +320,107 @@ module.exports = ( }; } - function finalizeHandler () { - const assets = Object.create(null); - getFlatFiles(mfs.data, assets, relocateLoader.getAssetPermissions); - // filter symlinks to existing assets + function finalizeHandler (stats) { + const files = Object.create(null); const symlinks = Object.create(null); + getFlatFiles(mfs.data, files, relocateLoader.getAssetPermissions); for (const [key, value] of Object.entries(relocateLoader.getSymlinks())) { const resolved = join(dirname(key), value); - if (resolved in assets) + if (resolved in files && key in files === false) symlinks[key] = value; } - delete assets[filename]; - delete assets[`${filename}.map`]; - let code = mfs.readFileSync(`/${filename}`, "utf8"); - let map = sourceMap ? mfs.readFileSync(`/${filename}.map`, "utf8") : null; - if (map) { - map = JSON.parse(map); - // make source map sources relative to output - map.sources = map.sources.map(source => { - // webpack:///webpack:/// happens too for some reason - while (source.startsWith('webpack:///')) - source = source.substr(11); - if (source.startsWith('./')) - source = source.substr(2); - if (source.startsWith('webpack/')) - return '/webpack/' + source.substr(8); - return sourceMapBasePrefix + source; - }); + for (const chunk of stats.compilation.chunks) { + finalizeOutput(chunk.files[0]); } - if (minify) { - const result = terser.minify(code, { - compress: false, - mangle: { - keep_classnames: true, - keep_fnames: true - }, - sourceMap: sourceMap ? { - content: map, - filename, - url: `${filename}.map` - } : false - }); - // For some reason, auth0 returns "undefined"! - // custom terser phase used over Webpack integration for this reason - if (result.code !== undefined) - ({ code, map } = { code: result.code, map: result.map }); - } - - if (v8cache) { - const { Script } = require('vm'); - assets[filename + '.cache'] = { source: new Script(code).createCachedData(), permissions: defaultPermissions }; - assets[filename + '.cache.js'] = { source: code, permissions: defaultPermissions }; + function finalizeOutput (filename) { + let code = mfs.readFileSync(`/${filename}`, "utf8"); + let map = sourceMap ? mfs.readFileSync(`/${filename}.map`, "utf8") : null; + if (map) { - assets[filename + '.map'] = { source: JSON.stringify(map), permissions: defaultPermissions }; - map = undefined; + map = JSON.parse(map); + // make source map sources relative to output + map.sources = map.sources.map(source => { + // webpack:///webpack:/// happens too for some reason + while (source.startsWith('webpack:///')) + source = source.substr(11); + if (source.startsWith('./')) + source = source.substr(2); + if (source.startsWith('webpack/')) + return '/webpack/' + source.substr(8); + return sourceMapBasePrefix + source; + }); + } + + if (minify) { + const result = terser.minify(code, { + compress: false, + mangle: { + keep_classnames: true, + keep_fnames: true + }, + sourceMap: sourceMap ? { + content: map, + filename, + url: `${filename}.map` + } : false + }); + // For some reason, auth0 returns "undefined"! + // custom terser phase used over Webpack integration for this reason + if (result.code !== undefined) + ({ code, map } = { code: result.code, map: result.map }); + } + + if (v8cache) { + const { Script } = require('vm'); + files[filename + '.cache'] = { source: new Script(code).createCachedData(), permissions: defaultPermissions }; + files[filename + '.cache.js'] = { source: code, permissions: defaultPermissions }; + if (map) { + files[filename + '.map'] = { source: JSON.stringify(map), permissions: defaultPermissions }; + map = undefined; + } + code = + `if (process.pkg || require('process').platform === 'win32') {\n` + + `module.exports=require('./${filename}.cache.js');\n` + + `} else {\n` + + `const { readFileSync, writeFileSync } = require('fs'), { Script } = require('vm'), { wrap } = require('module');\n` + + `const source = readFileSync(__dirname + '/${filename}.cache.js', 'utf-8'), cachedData = readFileSync(__dirname + '/${filename}.cache');\n` + + `const script = new Script(wrap(source), { cachedData });\n` + + `(script.runInThisContext())(exports, require, module, __filename, __dirname);\n` + + `process.on('exit', () => { try { writeFileSync(__dirname + '/${filename}.cache', script.createCachedData()); } catch(e) {} });\n` + + `}`; + } + + if (sourceMap && sourceMapRegister) { + code = `require('./sourcemap-register.js');` + code; + files['sourcemap-register.js'] = { source: fs.readFileSync(__dirname + "/sourcemap-register.js.cache.js"), permissions: defaultPermissions }; + } + + const entryPath = processedEntry[filename]; + let shebangMatch; + try { + shebangMatch = fs.readFileSync(entryPath).toString().match(shebangRegEx); + } + catch (e) { + try { + shebangMatch = fs.readFileSync(entryPath + '.js').toString().match(shebangRegEx); + } + catch (e) {} + } + if (shebangMatch) { + code = shebangMatch[0] + code; + // add a line offset to the sourcemap + if (map) + map.mappings = ";" + map.mappings; } - code = - `if (process.pkg || require('process').platform === 'win32') {\n` + - `module.exports=require('./${filename}.cache.js');\n` + - `} else {\n` + - `const { readFileSync, writeFileSync } = require('fs'), { Script } = require('vm'), { wrap } = require('module');\n` + - `const source = readFileSync(__dirname + '/${filename}.cache.js', 'utf-8'), cachedData = readFileSync(__dirname + '/${filename}.cache');\n` + - `const script = new Script(wrap(source), { cachedData });\n` + - `(script.runInThisContext())(exports, require, module, __filename, __dirname);\n` + - `process.on('exit', () => { try { writeFileSync(__dirname + '/${filename}.cache', script.createCachedData()); } catch(e) {} });\n` + - `}`; - } - - if (sourceMap && sourceMapRegister) { - code = `require('./sourcemap-register.js');` + code; - assets['sourcemap-register.js'] = { source: fs.readFileSync(__dirname + "/sourcemap-register.js.cache.js"), permissions: defaultPermissions }; - } - if (shebangMatch) { - code = shebangMatch[0] + code; - // add a line offset to the sourcemap - if (map) - map.mappings = ";" + map.mappings; + files[filename].source = code; + if (sourceMap && map) + files[filename + '.map'].source = JSON.stringify(map); } - return { code, map: map ? JSON.stringify(map) : undefined, assets, symlinks }; + return { files, symlinks }; } }; @@ -408,7 +435,7 @@ function getFlatFiles(mfsData, output, getAssetPermissions, curBase = "") { else if (!curPath.endsWith("/")) { output[curPath.substr(1)] = { source: mfsData[path], - permissions: getAssetPermissions(curPath.substr(1)) + permissions: getAssetPermissions(curPath.substr(1)) || defaultPermissions }; } } diff --git a/test/index.test.js b/test/index.test.js index 9bfbc272..9eb73775 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -21,8 +21,8 @@ for (const unitTest of fs.readdirSync(`${__dirname}/unit`)) { 'externaltest': 'externalmapped' } }).then( - async ({ code, assets }) => { - const actual = code + async ({ files }) => { + const actual = files['index.js'].source .trim() // Windows support .replace(/\r/g, ""); diff --git a/test/multi-entry.test.js b/test/multi-entry.test.js new file mode 100644 index 00000000..d4b8ee91 --- /dev/null +++ b/test/multi-entry.test.js @@ -0,0 +1,14 @@ +const ncc = global.coverage ? require("../src/index") : require("../"); +const path = require('path'); +const assert = require('assert'); + +jest.setTimeout(30000); + +it('Should support multiple entry points', async () => { + const { files } = await ncc({ + twilio: path.resolve('./test/integration/twilio.js'), + leveldown: path.resolve('./test/integration/leveldown.js') + }); + + assert.strictEqual(Object.keys(files).length, 4); +}); diff --git a/yarn.lock b/yarn.lock index afc249a9..25bf51dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1225,10 +1225,10 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -"@zeit/webpack-asset-relocator-loader@0.5.0-beta.6": - version "0.5.0-beta.6" - resolved "https://registry.yarnpkg.com/@zeit/webpack-asset-relocator-loader/-/webpack-asset-relocator-loader-0.5.0-beta.6.tgz#c25c8335bb8f836a90249196538b150c7cbe9228" - integrity sha512-pNV28syb5ebsGKKYwnitm4VQv+cbKIx8K5wBlX5WGbhhN6jw5vav3pYKLENdx6g/Cxy91ofGssL0CSzASXWkiw== +"@zeit/webpack-asset-relocator-loader@0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@zeit/webpack-asset-relocator-loader/-/webpack-asset-relocator-loader-0.5.1.tgz#796e454c18fefa1b08be322c7908e8fa2294ea3e" + integrity sha512-GbFVKSIru5vBF89Z6pSdc5qlCt5uXOsQM3/3ojx+oiqTqWY2mnUsVcJBOq8Xeyq0EPumClfVtdbFLuFCW7Yc9Q== dependencies: sourcemap-codec "^1.4.4"