diff --git a/readme.md b/readme.md index 8459442c..8defdb33 100644 --- a/readme.md +++ b/readme.md @@ -40,6 +40,9 @@ $ ncc build input.js -o dist Outputs the Node.js compact build of `input.js` into `dist/index.js`. +> Note: If the input file is using a `.cjs` extension, then so will the corresponding output file. +> This is useful for packages that want to use `.js` files as modules in native Node.js using +> a `"type": "module"` in the package.json file. #### Commands: ``` diff --git a/src/cli.js b/src/cli.js index f7b20d32..da3f7d55 100755 --- a/src/cli.js +++ b/src/cli.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const { resolve, relative, dirname, sep } = require("path"); +const { resolve, relative, dirname, sep, extname } = require("path"); const glob = require("glob"); const shebangRegEx = require("./utils/shebang"); const rimraf = require("rimraf"); @@ -50,7 +50,7 @@ else { api = true; } -function renderSummary(code, map, assets, outDir, buildTime) { +function renderSummary(code, map, assets, ext, 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; @@ -74,10 +74,10 @@ function renderSummary(code, map, assets, outDir, buildTime) { let indexRender = `${codeSize .toString() - .padStart(sizePadding, " ")}kB ${outDir}${"index.js"}`; + .padStart(sizePadding, " ")}kB ${outDir}index${ext}`; let indexMapRender = map ? `${mapSize .toString() - .padStart(sizePadding, " ")}kB ${outDir}${"index.js.map"}` : ''; + .padStart(sizePadding, " ")}kB ${outDir}index${ext}.map` : ''; let output = "", first = true; @@ -215,6 +215,7 @@ async function runCmd (argv, stdout, stderr) { let startTime = Date.now(); let ps; const buildFile = eval("require.resolve")(resolve(args._[1] || ".")); + const ext = buildFile.endsWith('.cjs') ? '.cjs' : '.js'; const ncc = require("./index.js")( buildFile, { @@ -241,16 +242,16 @@ async function runCmd (argv, stdout, stderr) { outDir = outDir || resolve("dist"); mkdirp.sync(outDir); - // remove all existing ".js" files in the out directory + // remove all existing ".js" and ".cjs" files in the out directory await Promise.all( (await new Promise((resolve, reject) => - glob(outDir + '/**/*.js', (err, files) => err ? reject(err) : resolve(files)) + glob(outDir + '/**/*.(js|cjs)', (err, files) => err ? reject(err) : resolve(files)) )).map(file => 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); + writeFileSync(`${outDir}/index${ext}`, code, { mode: code.match(shebangRegEx) ? 0o777 : 0o666 }); + if (map) writeFileSync(`${outDir}/index${ext}.map`, map); for (const asset of Object.keys(assets)) { const assetPath = outDir + "/" + asset; @@ -269,6 +270,7 @@ async function runCmd (argv, stdout, stderr) { code, map, assets, + ext, run ? "" : relative(process.cwd(), outDir), Date.now() - startTime, ) + '\n' @@ -292,7 +294,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${ext}`, { stdio: api ? 'pipe' : 'inherit' }); if (api) { diff --git a/src/index.js b/src/index.js index fd2e4c37..1e5043f7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,22 +1,19 @@ const resolve = require("resolve"); const fs = require("graceful-fs"); const crypto = require("crypto"); -const { sep, join, dirname } = require("path"); +const { join, dirname, 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'); // support glob graceful-fs fs.gracefulify(require("fs")); -const nodeBuiltins = new Set([...require("repl")._builtinLibs, "constants", "module", "timers", "console", "_stream_writable", "_stream_readable", "_stream_duplex"]); - const SUPPORTED_EXTENSIONS = [".js", ".json", ".node", ".mjs", ".ts", ".tsx"]; const hashOf = name => { @@ -36,7 +33,7 @@ module.exports = ( { cache, externals = [], - filename = "index.js", + filename = 'index' + (entry.endsWith('.cjs') ? '.cjs' : '.js'), minify = false, sourceMap = false, sourceMapRegister = true, @@ -49,6 +46,8 @@ module.exports = ( transpileOnly = false } = {} ) => { + const ext = extname(filename); + if (!quiet) { console.log(`ncc: Version ${nccVersion}`); console.log(`ncc: Compiling file ${filename}`); @@ -61,11 +60,11 @@ module.exports = ( const existingAssetNames = [filename]; if (sourceMap) { existingAssetNames.push(`${filename}.map`); - existingAssetNames.push('sourcemap-register.js'); + existingAssetNames.push(`sourcemap-register${ext}`); } if (v8cache) { existingAssetNames.push(`${filename}.cache`); - existingAssetNames.push(`${filename}.cache.js`); + existingAssetNames.push(`${filename}.cache${ext}`); } const resolvePlugins = []; // add TsconfigPathsPlugin to support `paths` resolution in tsconfig @@ -140,7 +139,8 @@ module.exports = ( target: "node", output: { path: "/", - filename, + // Webpack only emits sourcemaps for files ending in .js + filename: ext === '.cjs' ? filename + '.js' : filename, libraryTarget: "commonjs2" }, resolve: { @@ -341,10 +341,12 @@ module.exports = ( if (resolved in assets) 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; + // Webpack only emits sourcemaps for .js files + // so we need to adjust the .cjs extension handling + delete assets[filename + (ext === '.cjs' ? '.js' : '')]; + delete assets[`${filename}${ext === '.cjs' ? '.js' : ''}.map`]; + let code = mfs.readFileSync(`/${filename}${ext === '.cjs' ? '.js' : ''}`, "utf8"); + let map = sourceMap ? mfs.readFileSync(`/${filename}${ext === '.cjs' ? '.js' : ''}.map`, "utf8") : null; if (map) { map = JSON.parse(map); @@ -385,15 +387,15 @@ module.exports = ( if (v8cache) { const { Script } = require('vm'); - assets[filename + '.cache'] = { source: new Script(code).createCachedData(), permissions: defaultPermissions }; - assets[filename + '.cache.js'] = { source: code, permissions: defaultPermissions }; + assets[`${filename}.cache`] = { source: new Script(code).createCachedData(), permissions: defaultPermissions }; + assets[`${filename}.cache${ext}`] = { source: code, permissions: defaultPermissions }; if (map) { assets[filename + '.map'] = { source: JSON.stringify(map), permissions: defaultPermissions }; map = undefined; } code = `const { readFileSync, writeFileSync } = require('fs'), { Script } = require('vm'), { wrap } = require('module');\n` + - `const source = readFileSync(__dirname + '/${filename}.cache.js', 'utf-8');\n` + + `const source = readFileSync(__dirname + '/${filename}.cache${ext}', 'utf-8');\n` + `const cachedData = !process.pkg && require('process').platform !== 'win32' && readFileSync(__dirname + '/${filename}.cache');\n` + `const script = new Script(wrap(source), cachedData ? { cachedData } : {});\n` + `(script.runInThisContext())(exports, require, module, __filename, __dirname);\n` + @@ -401,8 +403,8 @@ module.exports = ( } if (sourceMap && sourceMapRegister) { - code = `require('./sourcemap-register.js');` + code; - assets['sourcemap-register.js'] = { source: fs.readFileSync(__dirname + "/sourcemap-register.js.cache.js"), permissions: defaultPermissions }; + code = `require('./sourcemap-register${ext}');` + code; + assets[`sourcemap-register${ext}`] = { source: fs.readFileSync(`${__dirname}/sourcemap-register.js.cache.js`), permissions: defaultPermissions }; } if (shebangMatch) { diff --git a/test/cli.js b/test/cli.js index 0aeda08e..54e6dda9 100644 --- a/test/cli.js +++ b/test/cli.js @@ -51,5 +51,17 @@ { args: ["run", "-t", "test/fixtures/with-type-errors/ts-error.ts"], expect: { code: 0 } + }, + { + args: ["build", "-o", "tmp", "test/fixtures/test.cjs"], + expect (code, stdout, stderr) { + return stdout.toString().indexOf('tmp/index.cjs') !== -1; + } + }, + { + args: ["build", "-o", "tmp", "test/fixtures/test.mjs"], + expect (code, stdout, stderr) { + return stdout.toString().indexOf('tmp/index.js') !== -1; + } } ] \ No newline at end of file diff --git a/test/fixtures/test.cjs b/test/fixtures/test.cjs new file mode 100644 index 00000000..37a46484 --- /dev/null +++ b/test/fixtures/test.cjs @@ -0,0 +1 @@ +module.exports = 'test'; diff --git a/test/fixtures/test.mjs b/test/fixtures/test.mjs new file mode 100644 index 00000000..37a46484 --- /dev/null +++ b/test/fixtures/test.mjs @@ -0,0 +1 @@ +module.exports = 'test';