diff --git a/.travis.yml b/.travis.yml index 51d18a6c9ef..405134e68cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ language: node_js node_js: - "12" - "10" - - "8" cache: directories: - ~/.npm @@ -20,9 +19,6 @@ matrix: - os: linux node_js: "10" env: JOB_PART=integration - - os: linux - node_js: "8" - env: JOB_PART=integration before_install: - "[[ $(node -v) =~ ^v9.*$ ]] || npm install -g npm@latest" # skipped when using node 9 diff --git a/azure-pipelines-template.yml b/azure-pipelines-template.yml index e752c32868e..96d81cc753d 100644 --- a/azure-pipelines-template.yml +++ b/azure-pipelines-template.yml @@ -8,9 +8,7 @@ jobs: node-12: node_version: ^12.0.0 node-10: - node_version: ^10.10.0 - node-8: - node_version: ^8.12.0 + node_version: ^10.13.0 steps: - task: NodeTool@0 inputs: diff --git a/lib/bootstrap.js b/lib/bootstrap.js index a18995bee1f..7ccbcfe3860 100644 --- a/lib/bootstrap.js +++ b/lib/bootstrap.js @@ -1,11 +1,14 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ +const execa = require('execa'); const WebpackCLI = require('./webpack-cli'); const { core, commands } = require('./utils/cli-flags'); const cmdArgs = require('command-line-args'); const logger = require('./utils/logger'); +const parseArgs = require('./utils/parse-args'); require('./utils/process-log'); +const cliPath = require.resolve('../cli.js'); const isFlagPresent = (args, flag) => args.find(arg => [flag, `--${flag}`].includes(arg)); const isArgCommandName = (arg, cmd) => arg === cmd.name || arg === cmd.alias; const removeCmdFromArgs = (args, cmd) => args.filter(arg => !isArgCommandName(arg, cmd)); @@ -45,6 +48,7 @@ async function runCLI(cli, commandIsUsed) { let args; const helpFlagExists = isFlagPresent(process.argv, 'help'); const versionFlagExists = isFlagPresent(process.argv, 'version'); + const nodeArgsExists = process.argv.find(arg => arg.includes('--node-args')); if (helpFlagExists) { cli.runHelp(process.argv); @@ -52,6 +56,17 @@ async function runCLI(cli, commandIsUsed) { } else if (versionFlagExists) { cli.runVersion(); return; + } else if (nodeArgsExists) { + const [, , ...rawArgs] = process.argv; + const { cliArgs, nodeArgs } = parseArgs(rawArgs); + + try { + const childProcess = execa('node', [...nodeArgs, cliPath, ...cliArgs], { stdio: 'inherit' }); + await childProcess; + process.exit(); + } catch (e) { + process.exit(e.exitCode); + } } if (commandIsUsed) { diff --git a/lib/utils/cli-flags.js b/lib/utils/cli-flags.js index c0b77a10b50..2ee462aa4f2 100644 --- a/lib/utils/cli-flags.js +++ b/lib/utils/cli-flags.js @@ -273,6 +273,14 @@ module.exports = { group: BASIC_GROUP, description: 'Get current version', }, + { + name: 'node-args', + usage: '--node-args "--max-old-space-size=1024"', + type: String, + multiple: true, + group: BASIC_GROUP, + description: 'NodeJS flags', + }, /* { name: "analyze", type: Boolean, diff --git a/lib/utils/parse-args.js b/lib/utils/parse-args.js new file mode 100644 index 00000000000..3285410ae70 --- /dev/null +++ b/lib/utils/parse-args.js @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + +/** + * Parse cli args and split these to args for node js and the rest + * + * @param {string[]} rawArgs raw cli args + * @returns {{cliArgs: string[], nodeArgs: string[]}} cli and nodejs args + */ +module.exports = rawArgs => { + const cliArgs = []; + const nodeArgs = []; + let isNodeArg = false; + + for (const value of rawArgs) { + if (value === '--node-args') { + isNodeArg = true; + } else if (value.startsWith('--node-args=')) { + const [, argValue] = value.match(/^--node-args="?(.+?)"?$/); + nodeArgs.push(argValue); + } else if (isNodeArg) { + isNodeArg = false; + nodeArgs.push(...value.split(' ')); + } else { + cliArgs.push(value); + } + } + + return { cliArgs, nodeArgs }; +}; diff --git a/package-lock.json b/package-lock.json index b19ff49e39c..4532ef4bf7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6466,7 +6466,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/execa/-/execa-3.3.0.tgz", "integrity": "sha512-j5Vit5WZR/cbHlqU97+qcnw9WHRCIL4V1SVe75VcHcD1JRBdt8fv0zw89b7CQHQdUHTt2VjuhcF5ibAgVOxqpg==", - "dev": true, "requires": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -6484,7 +6483,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6495,7 +6493,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -6503,20 +6500,17 @@ "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "npm-run-path": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.0.tgz", "integrity": "sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ==", - "dev": true, "requires": { "path-key": "^3.0.0" } @@ -6525,7 +6519,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -6533,20 +6526,17 @@ "p-finally": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==" }, "path-key": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz", - "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==", - "dev": true + "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==" }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -6554,14 +6544,12 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "which": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -8600,8 +8588,7 @@ "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" }, "humanize-ms": { "version": "1.2.1", @@ -14111,8 +14098,7 @@ "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, "strip-indent": { "version": "2.0.0", diff --git a/package.json b/package.json index dcfece0d599..2ae43b85a48 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "main": "cli.js", "engines": { - "node": ">=8.0.0" + "node": ">=10.13.0" }, "keywords": [ "webpack", @@ -119,6 +119,7 @@ "cli-table3": "^0.5.1", "command-line-args": "^5.1.1", "command-line-usage": "^6.1.0", + "execa": "^3.2.0", "import-local": "^3.0.2", "interpret": "^2.0.0", "terser-webpack-plugin": "^2.3.2", @@ -144,7 +145,6 @@ "eslint-config-prettier": "^6.5.0", "eslint-plugin-node": "^10.0.0", "eslint-plugin-prettier": "^3.1.1", - "execa": "^3.2.0", "husky": "^3.0.9", "jest": "^24.9.0", "jest-cli": "^24.9.0", diff --git a/test/help/__snapshots__/help-single-arg.test.js.snap b/test/help/__snapshots__/help-single-arg.test.js.snap index 4d120ade45d..e983d7fb240 100644 --- a/test/help/__snapshots__/help-single-arg.test.js.snap +++ b/test/help/__snapshots__/help-single-arg.test.js.snap @@ -44,6 +44,7 @@ Options -d, --dev Run development build -p, --prod Run production build --version Get current version + --node-args string[] NodeJS flags Made with ♥️ by the webpack team " `; diff --git a/test/node/a.js b/test/node/a.js new file mode 100644 index 00000000000..735d820f253 --- /dev/null +++ b/test/node/a.js @@ -0,0 +1 @@ +module.exports = 'a.js'; diff --git a/test/node/bootstrap.js b/test/node/bootstrap.js new file mode 100644 index 00000000000..0980f54d442 --- /dev/null +++ b/test/node/bootstrap.js @@ -0,0 +1 @@ +console.log('---from bootstrap.js---'); diff --git a/test/node/bootstrap2.js b/test/node/bootstrap2.js new file mode 100644 index 00000000000..5105ea40c01 --- /dev/null +++ b/test/node/bootstrap2.js @@ -0,0 +1 @@ +console.log('---from bootstrap2.js---'); diff --git a/test/node/node.test.js b/test/node/node.test.js new file mode 100644 index 00000000000..d2396935b37 --- /dev/null +++ b/test/node/node.test.js @@ -0,0 +1,47 @@ +'use strict'; +const { stat } = require('fs'); +const { resolve, sep } = require('path'); +const { run, extractSummary } = require('../utils/test-utils'); +const parseArgs = require('../../lib/utils/parse-args'); + +describe('node flags', () => { + it('parseArgs helper must work correctly', () => { + [ + { + rawArgs: ['--foo', '--bar', '--baz=quux'], + expectedCliArgs: ['--foo', '--bar', '--baz=quux'], + expectedNodeArgs: [], + }, + { + rawArgs: ['--foo', '--bar', '--baz=quux', '--node-args', '--name1=value1', '--node-args', '--name2 value2'], + expectedCliArgs: ['--foo', '--bar', '--baz=quux'], + expectedNodeArgs: ['--name1=value1', '--name2', 'value2'], + }, + { + rawArgs: ['--node-args', '--name1=value1', '--node-args', '--name2="value2"', '--node-args', '--name3 value3', '--node-args', '-k v'], + expectedCliArgs: [], + expectedNodeArgs: ['--name1=value1', '--name2="value2"', '--name3', 'value3', '-k', 'v'], + }, + ].map(({ rawArgs, expectedNodeArgs, expectedCliArgs }) => { + const { nodeArgs, cliArgs } = parseArgs(rawArgs); + expect(nodeArgs).toEqual(expectedNodeArgs); + expect(cliArgs).toEqual(expectedCliArgs); + }); + }); + + it('is able to pass the options flags to node js', done => { + const { stdout } = run(__dirname, ['--node-args', `--require=${resolve(__dirname, 'bootstrap.js')}`, '--node-args', `-r ${resolve(__dirname, 'bootstrap2.js')}`, '--output', './bin/[name].bundle.js'], false); + expect(stdout).toContain('---from bootstrap.js---'); + expect(stdout).toContain('---from bootstrap2.js---'); + const summary = extractSummary(stdout); + const outputDir = 'node/bin'; + const outDirectoryFromCompiler = summary['Output Directory'].split(sep); + const outDirToMatch = outDirectoryFromCompiler.slice(outDirectoryFromCompiler.length - 2, outDirectoryFromCompiler.length).join('/'); + expect(outDirToMatch).toContain(outputDir); + stat(resolve(__dirname, './bin/main.bundle.js'), (err, stats) => { + expect(err).toBe(null); + expect(stats.isFile()).toBe(true); + done(); + }); + }); +}); diff --git a/test/node/webpack.config.js b/test/node/webpack.config.js new file mode 100644 index 00000000000..b58f8a91f0d --- /dev/null +++ b/test/node/webpack.config.js @@ -0,0 +1,9 @@ +const { resolve } = require('path'); + +module.exports = { + entry: './a.js', + output: { + path: resolve(__dirname, 'binary'), + filename: 'a.bundle.js', + }, +};