merge: webpack updates (#9870)

* refactor(webpack): use global require for checking hmr chunks

* wip: watchstate plugin adjustments

* feat(webpack): emit hrm boot status

* chore: bump deps

* fix(hmr): emit boot log at boot instead of the 1st livesync

* chore(release): @nativescript/webpack 5.0.8-alpha.0

* refactor(webpack): use real modules and deprecate virtual modules

* feat(webpack): allow disabling nativescriptLibPath warning with a boolean

* fix(webpack): remove copy rules that don't match any files to avoid false watch triggers

* feat(webpack): add --env.stats to disable printing stats
primarily used internally by preview-cli

* chore(release): @nativescript/webpack 5.0.8-alpha.2

* fix: revert copy rule glob filter

* chore(release): @nativescript/webpack 5.0.8-alpha.3
This commit is contained in:
Igor Randjelovic
2022-07-26 01:05:55 +02:00
committed by GitHub
15 changed files with 133 additions and 128 deletions

View File

@ -302,16 +302,6 @@ exports[`javascript configuration for android 1`] = `
), ),
/* config.plugin('WatchStatePlugin') */ /* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(), new WatchStatePlugin(),
/* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_javascript__') */
new ContextExclusionPlugin(
/__@nativescript_webpack_virtual_entry_javascript__.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__@nativescript_webpack_virtual_entry_javascript__': '// VIRTUAL ENTRY START\\\\nrequire(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/);\\\\nglobal.registerWebpackModules(context);\\\\n// VIRTUAL ENTRY END'
}
),
/* config.plugin('ContextExclusionPlugin|exclude_files') */ /* config.plugin('ContextExclusionPlugin|exclude_files') */
new ContextExclusionPlugin( new ContextExclusionPlugin(
/\\\\b_.+\\\\./ /\\\\b_.+\\\\./
@ -320,7 +310,7 @@ exports[`javascript configuration for android 1`] = `
entry: { entry: {
bundle: [ bundle: [
'@nativescript/core/globals/index', '@nativescript/core/globals/index',
'__jest__/src/__@nativescript_webpack_virtual_entry_javascript__', '__jest__/node_modules/@nativescript/webpack/dist/stubs/virtual-entry-javascript',
'@nativescript/core/bundle-entry-points', '@nativescript/core/bundle-entry-points',
'__jest__/src/app.js', '__jest__/src/app.js',
'@nativescript/core/ui/frame', '@nativescript/core/ui/frame',
@ -632,16 +622,6 @@ exports[`javascript configuration for ios 1`] = `
), ),
/* config.plugin('WatchStatePlugin') */ /* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(), new WatchStatePlugin(),
/* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_javascript__') */
new ContextExclusionPlugin(
/__@nativescript_webpack_virtual_entry_javascript__.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__@nativescript_webpack_virtual_entry_javascript__': '// VIRTUAL ENTRY START\\\\nrequire(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/);\\\\nglobal.registerWebpackModules(context);\\\\n// VIRTUAL ENTRY END'
}
),
/* config.plugin('ContextExclusionPlugin|exclude_files') */ /* config.plugin('ContextExclusionPlugin|exclude_files') */
new ContextExclusionPlugin( new ContextExclusionPlugin(
/\\\\b_.+\\\\./ /\\\\b_.+\\\\./
@ -650,7 +630,7 @@ exports[`javascript configuration for ios 1`] = `
entry: { entry: {
bundle: [ bundle: [
'@nativescript/core/globals/index', '@nativescript/core/globals/index',
'__jest__/src/__@nativescript_webpack_virtual_entry_javascript__', '__jest__/node_modules/@nativescript/webpack/dist/stubs/virtual-entry-javascript',
'@nativescript/core/bundle-entry-points', '@nativescript/core/bundle-entry-points',
'__jest__/src/app.js' '__jest__/src/app.js'
], ],

View File

@ -302,16 +302,6 @@ exports[`typescript configuration for android 1`] = `
), ),
/* config.plugin('WatchStatePlugin') */ /* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(), new WatchStatePlugin(),
/* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_typescript__') */
new ContextExclusionPlugin(
/__@nativescript_webpack_virtual_entry_typescript__.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__@nativescript_webpack_virtual_entry_typescript__': '// VIRTUAL ENTRY START\\\\nrequire(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /\\\\\\\\.(xml|js|(?<!\\\\\\\\.d\\\\\\\\.)ts|s?css)$/);\\\\nglobal.registerWebpackModules(context);\\\\n// VIRTUAL ENTRY END'
}
),
/* config.plugin('ContextExclusionPlugin|exclude_files') */ /* config.plugin('ContextExclusionPlugin|exclude_files') */
new ContextExclusionPlugin( new ContextExclusionPlugin(
/\\\\b_.+\\\\./ /\\\\b_.+\\\\./
@ -320,7 +310,7 @@ exports[`typescript configuration for android 1`] = `
entry: { entry: {
bundle: [ bundle: [
'@nativescript/core/globals/index', '@nativescript/core/globals/index',
'__jest__/src/__@nativescript_webpack_virtual_entry_typescript__', '__jest__/node_modules/@nativescript/webpack/dist/stubs/virtual-entry-typescript.js',
'@nativescript/core/bundle-entry-points', '@nativescript/core/bundle-entry-points',
'__jest__/src/app.js', '__jest__/src/app.js',
'@nativescript/core/ui/frame', '@nativescript/core/ui/frame',
@ -632,16 +622,6 @@ exports[`typescript configuration for ios 1`] = `
), ),
/* config.plugin('WatchStatePlugin') */ /* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(), new WatchStatePlugin(),
/* config.plugin('ContextExclusionPlugin|__@nativescript_webpack_virtual_entry_typescript__') */
new ContextExclusionPlugin(
/__@nativescript_webpack_virtual_entry_typescript__.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__@nativescript_webpack_virtual_entry_typescript__': '// VIRTUAL ENTRY START\\\\nrequire(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /\\\\\\\\.(xml|js|(?<!\\\\\\\\.d\\\\\\\\.)ts|s?css)$/);\\\\nglobal.registerWebpackModules(context);\\\\n// VIRTUAL ENTRY END'
}
),
/* config.plugin('ContextExclusionPlugin|exclude_files') */ /* config.plugin('ContextExclusionPlugin|exclude_files') */
new ContextExclusionPlugin( new ContextExclusionPlugin(
/\\\\b_.+\\\\./ /\\\\b_.+\\\\./
@ -650,7 +630,7 @@ exports[`typescript configuration for ios 1`] = `
entry: { entry: {
bundle: [ bundle: [
'@nativescript/core/globals/index', '@nativescript/core/globals/index',
'__jest__/src/__@nativescript_webpack_virtual_entry_typescript__', '__jest__/node_modules/@nativescript/webpack/dist/stubs/virtual-entry-typescript.js',
'@nativescript/core/bundle-entry-points', '@nativescript/core/bundle-entry-points',
'__jest__/src/app.js' '__jest__/src/app.js'
], ],

View File

@ -1,4 +1,6 @@
import Config from 'webpack-chain'; import Config from 'webpack-chain';
import path from 'path';
import fs from 'fs';
import typescript from '../../src/configuration/typescript'; import typescript from '../../src/configuration/typescript';
import { init } from '../../src'; import { init } from '../../src';
@ -15,29 +17,30 @@ describe('typescript configuration', () => {
}); });
} }
it('filter typescript declaration files', () => { it('filters typescript declaration files', () => {
init({ init({
ios: true, ios: true,
}); });
const tsConfig = typescript(new Config()); // const tsConfig = typescript(new Config());
let regex: RegExp; let regex: RegExp;
// Get the filterRE from the typescript configuration const virtualEntryPath = path.join(
tsConfig.plugin('VirtualModulesPlugin').tap((args) => { __dirname,
const options = args[0]; '../../src/stubs/virtual-entry-typescript.js'
const virtualConfig: string = options[Object.keys(options)[0]]; );
const filterLine = virtualConfig const virtualEntry = fs.readFileSync(virtualEntryPath);
.split('\n')
.find((v) => v.includes('filter'));
const matches = filterLine.match(/\/(?<filter>\S+)\//);
if (matches) { const filterLine = virtualEntry
regex = new RegExp(matches.groups.filter); .toString()
} .split('\n')
.find((v) => v.includes('filter'));
return args; const matches = filterLine.match(/\/(?<filter>\S+)\//);
});
if (matches) {
regex = new RegExp(matches.groups.filter);
}
expect(regex).toBeDefined(); expect(regex).toBeDefined();

View File

@ -1,6 +1,6 @@
{ {
"name": "@nativescript/webpack", "name": "@nativescript/webpack",
"version": "5.0.7", "version": "5.0.8-alpha.3",
"private": false, "private": false,
"main": "dist/index.js", "main": "dist/index.js",
"files": [ "files": [
@ -29,17 +29,17 @@
"css": "^3.0.0", "css": "^3.0.0",
"css-loader": "^6.0.0", "css-loader": "^6.0.0",
"dotenv-webpack": "^7.0.0", "dotenv-webpack": "^7.0.0",
"fork-ts-checker-webpack-plugin": "^6.0.0", "fork-ts-checker-webpack-plugin": "^7.0.0",
"loader-utils": "^2.0.0", "loader-utils": "^2.0.0 || ^3.0.0",
"lodash.get": "^4.0.0", "lodash.get": "^4.0.0",
"micromatch": "^4.0.0", "micromatch": "^4.0.0",
"postcss": "^8.0.0", "postcss": "^8.0.0",
"postcss-import": "^14.0.0", "postcss-import": "^14.0.0",
"postcss-loader": "^6.0.0", "postcss-loader": "^7.0.0",
"raw-loader": "^4.0.0", "raw-loader": "^4.0.0",
"react-refresh": "~0.11.0", "react-refresh": "~0.11.0",
"sass": "^1.0.0", "sass": "^1.0.0",
"sass-loader": "^12.0.0", "sass-loader": "^13.0.0",
"sax": "^1.0.0", "sax": "^1.0.0",
"source-map": "^0.7.0", "source-map": "^0.7.0",
"terser-webpack-plugin": "^5.0.0", "terser-webpack-plugin": "^5.0.0",
@ -56,17 +56,17 @@
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^13.1.2", "@angular-devkit/build-angular": "^13.1.2",
"@types/css": "0.0.33", "@types/css": "0.0.33",
"@types/jest": "27.0.1", "@types/jest": "27.5.1",
"@types/loader-utils": "2.0.3", "@types/loader-utils": "2.0.3",
"@types/lodash.get": "4.4.6", "@types/lodash.get": "4.4.7",
"@types/micromatch": "4.0.2", "@types/micromatch": "4.0.2",
"@types/sax": "1.2.3", "@types/sax": "1.2.4",
"@types/terser-webpack-plugin": "5.2.0", "@types/terser-webpack-plugin": "5.2.0",
"@types/webpack-virtual-modules": "0.1.1", "@types/webpack-virtual-modules": "0.1.1",
"jest": "27.1.1", "jest": "28.1.0",
"jest-matcher-utils": "27.1.1", "jest-matcher-utils": "28.1.0",
"nativescript-vue-template-compiler": "2.9.0", "nativescript-vue-template-compiler": "2.9.2",
"ts-jest": "27.0.5", "ts-jest": "28.0.3",
"typescript": "4.7.3" "typescript": "4.7.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

@ -56,6 +56,7 @@ program
env['env'] = options.env; env['env'] = options.env;
} }
env['stats'] ??= true;
env['watch'] ??= options.watch; env['watch'] ??= options.watch;
// if --env.config is passed, we'll set an environment // if --env.config is passed, we'll set an environment
@ -108,13 +109,15 @@ program
// Set the process exit code depending on errors // Set the process exit code depending on errors
process.exitCode = stats.hasErrors() ? 1 : 0; process.exitCode = stats.hasErrors() ? 1 : 0;
console.log( if (env.stats) {
stats.toString({ console.log(
chunks: false, stats.toString({
colors: true, chunks: false,
errorDetails: env.verbose, colors: true,
}) errorDetails: env.verbose,
); })
);
}
// if webpack profile is enabled we write the stats to a JSON file // if webpack profile is enabled we write the stats to a JSON file
if (configuration.profile || env.profile) { if (configuration.profile || env.profile) {
@ -141,7 +144,7 @@ program
}; };
if (options.watch) { if (options.watch) {
console.log('webpack is watching the files...'); env.stats && console.log('webpack is watching the files...');
compiler.watch( compiler.watch(
configuration.watchOptions ?? {}, configuration.watchOptions ?? {},
webpackCompilationCallback webpackCompilationCallback

View File

@ -1,26 +1,18 @@
import Config from 'webpack-chain'; import Config from 'webpack-chain';
import { getEntryPath, getEntryDirPath } from '../helpers/platform'; import { getEntryPath, getEntryDirPath } from '../helpers/platform';
import { addVirtualEntry } from '../helpers/virtualModules';
import { chainedSetAddAfter } from '../helpers/chain'; import { chainedSetAddAfter } from '../helpers/chain';
import { env as _env, IWebpackEnv } from '../index'; import { env as _env, IWebpackEnv } from '../index';
import { ContextExclusionPlugin } from 'webpack'; import { ContextExclusionPlugin } from 'webpack';
import base from './base'; import base from './base';
import path from 'path';
export default function (config: Config, env: IWebpackEnv = _env): Config { export default function (config: Config, env: IWebpackEnv = _env): Config {
base(config, env); base(config, env);
const entryPath = getEntryPath(); const entryPath = getEntryPath();
const filterRE = '/.(xml|js|s?css)$/'; const virtualEntryPath = path.resolve(
const virtualEntryPath = addVirtualEntry( __dirname,
config, '../stubs/virtual-entry-javascript'
'javascript',
`
// VIRTUAL ENTRY START
require('@nativescript/core/bundle-entry-points')
const context = require.context("~/", /* deep: */ true, /* filter: */ ${filterRE});
global.registerWebpackModules(context);
// VIRTUAL ENTRY END
`
); );
// exclude files starting with _ from require.context // exclude files starting with _ from require.context

View File

@ -1,26 +1,18 @@
import Config from 'webpack-chain'; import Config from 'webpack-chain';
import { getEntryDirPath, getEntryPath } from '../helpers/platform'; import { getEntryDirPath, getEntryPath } from '../helpers/platform';
import { addVirtualEntry } from '../helpers/virtualModules';
import { chainedSetAddAfter } from '../helpers/chain'; import { chainedSetAddAfter } from '../helpers/chain';
import { env as _env, IWebpackEnv } from '../index'; import { env as _env, IWebpackEnv } from '../index';
import { ContextExclusionPlugin } from 'webpack'; import { ContextExclusionPlugin } from 'webpack';
import base from './base'; import base from './base';
import path from 'path';
export default function (config: Config, env: IWebpackEnv = _env): Config { export default function (config: Config, env: IWebpackEnv = _env): Config {
base(config, env); base(config, env);
const entryPath = getEntryPath(); const entryPath = getEntryPath();
const filterRE = '/\\.(xml|js|(?<!\\.d\\.)ts|s?css)$/'; const virtualEntryPath = path.resolve(
const virtualEntryPath = addVirtualEntry( __dirname,
config, '../stubs/virtual-entry-typescript.js'
'typescript',
`
// VIRTUAL ENTRY START
require('@nativescript/core/bundle-entry-points')
const context = require.context("~/", /* deep: */ true, /* filter: */ ${filterRE});
global.registerWebpackModules(context);
// VIRTUAL ENTRY END
`
); );
// exclude files starting with _ from require.context // exclude files starting with _ from require.context

View File

@ -3,16 +3,22 @@ import { env } from '../index';
function getCLILib() { function getCLILib() {
if (!env.nativescriptLibPath) { if (!env.nativescriptLibPath) {
warnOnce( if (typeof env.nativescriptLibPath !== 'boolean') {
'getCLILib', warnOnce(
` 'getCLILib',
Cannot find NativeScript CLI path. Make sure --env.nativescriptLibPath is passed `
` Cannot find NativeScript CLI path. Make sure --env.nativescriptLibPath is passed
); `
);
}
return false; return false;
} }
return require(env.nativescriptLibPath); if (typeof env.nativescriptLibPath === 'boolean') {
return false;
}
return require(env.nativescriptLibPath as string);
} }
/** /**

View File

@ -1,6 +1,7 @@
import CopyWebpackPlugin from 'copy-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin';
import { basename, relative, resolve } from 'path'; import { basename, relative, resolve } from 'path';
import Config from 'webpack-chain'; import Config from 'webpack-chain';
import { sync as globbySync } from 'globby';
import { getProjectRootPath } from './project'; import { getProjectRootPath } from './project';
import { getEntryDirPath } from './platform'; import { getEntryDirPath } from './platform';
@ -70,6 +71,17 @@ export function applyCopyRules(config: Config) {
config.plugin('CopyWebpackPlugin').use(CopyWebpackPlugin, [ config.plugin('CopyWebpackPlugin').use(CopyWebpackPlugin, [
{ {
patterns: Array.from(copyRules) patterns: Array.from(copyRules)
// reverted: removes valid rules occasionally (ie fonts)
// todo: re-visit in future...
// .filter((glob) => {
// if (process.env.NODE_ENV === 'test') {
// return true;
// }
// // remove rules that do not match any paths
// // prevents webpack watch mode from firing
// // due to "removed" paths.
// return globbySync(glob).length > 0;
// })
.map((glob) => ({ .map((glob) => ({
from: glob, from: glob,
context: entryDir, context: entryDir,

View File

@ -8,6 +8,9 @@ import { getEntryDirPath } from './platform';
import dedent from 'ts-dedent'; import dedent from 'ts-dedent';
import { getProjectFilePath } from './project'; import { getProjectFilePath } from './project';
/**
* @deprecated Virtual entries are not recommended by the webpack team, use real files instead. For example, resolve a path in node_modules if necessary.
*/
export function addVirtualEntry( export function addVirtualEntry(
config: Config, config: Config,
name: string, name: string,
@ -20,6 +23,9 @@ export function addVirtualEntry(
); );
} }
/**
* @deprecated Virtual modules are not recommended by the webpack team, use real files instead. For example, resolve a path in node_modules if necessary.
*/
export function addVirtualModule( export function addVirtualModule(
config: Config, config: Config,
name: string, name: string,

View File

@ -28,7 +28,7 @@ export interface IWebpackEnv {
appResourcesPath?: string; appResourcesPath?: string;
appComponents?: string[]; appComponents?: string[];
nativescriptLibPath?: string; nativescriptLibPath?: string | boolean;
android?: boolean; android?: boolean;
ios?: boolean; ios?: boolean;
@ -46,6 +46,9 @@ export interface IWebpackEnv {
// enable webpack profiling // enable webpack profiling
profile?: boolean; profile?: boolean;
// print webpack stats (default: true)
stats?: boolean;
// misc // misc
replace?: string[] | string; replace?: string[] | string;
watchNodeModules?: boolean; watchNodeModules?: boolean;

View File

@ -5,6 +5,7 @@
if (module.hot) { if (module.hot) {
let hash = __webpack_require__.h(); let hash = __webpack_require__.h();
let hmrBootEmittedSymbol = Symbol.for('HMRBootEmitted');
const logVerbose = (title: string, ...info: any) => { const logVerbose = (title: string, ...info: any) => {
if (__NS_ENV_VERBOSE__) { if (__NS_ENV_VERBOSE__) {
@ -19,7 +20,7 @@ if (module.hot) {
const setStatus = ( const setStatus = (
hash: string, hash: string,
status: 'success' | 'failure', status: 'success' | 'failure' | 'boot',
message?: string, message?: string,
...info: any ...info: any
): boolean => { ): boolean => {
@ -93,7 +94,7 @@ if (module.hot) {
const requireExists = (path) => { const requireExists = (path) => {
try { try {
__non_webpack_require__(path); global['require'](path);
return true; return true;
} catch (err) { } catch (err) {
return false; return false;
@ -112,10 +113,18 @@ if (module.hot) {
logVerbose('LiveSync'); logVerbose('LiveSync');
if (!hasUpdate()) { if (!hasUpdate()) {
return; return false;
} }
await checkAndApply(); if (!(await checkAndApply())) {
originalOnLiveSync(); return false;
}
await originalOnLiveSync();
}; };
if (!global[hmrBootEmittedSymbol]) {
global[hmrBootEmittedSymbol] = true;
setStatus(hash, 'boot', 'HMR Enabled - waiting for changes...');
}
} }

View File

@ -22,7 +22,7 @@ export class WatchStatePlugin {
callback(); callback();
if (isWatchMode) { if (isWatchMode) {
console.log(messages.changeDetected); env.stats && console.log(messages.changeDetected);
if (env.verbose) { if (env.verbose) {
if (compiler.modifiedFiles) { if (compiler.modifiedFiles) {
@ -44,9 +44,10 @@ export class WatchStatePlugin {
compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) { compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) {
callback(); callback();
console.log( env.stats &&
isWatchMode ? messages.startWatching : messages.compilationComplete console.log(
); isWatchMode ? messages.startWatching : messages.compilationComplete
);
// Do not notify the CLI if the compilation failed // Do not notify the CLI if the compilation failed
const stats = compilation.getStats(); const stats = compilation.getStats();
@ -55,16 +56,24 @@ export class WatchStatePlugin {
} }
// logic taken from CleanWebpackPlugin // logic taken from CleanWebpackPlugin
const assets = // const assets =
stats.toJson( // stats.toJson(
{ // {
assets: true, // assets: true,
}, // },
true // true
).assets || []; // ).assets || [];
const assetList = assets.map((asset) => asset.name); // const assetList = assets.map((asset) => asset.name);
// const emittedAssets = Array.from(compilation.emittedAssets);
const assetList = Object.keys(compilation.assets);
const emittedAssets = Array.from(compilation.emittedAssets); const emittedAssets = Array.from(compilation.emittedAssets);
if (!prevAssets.length && emittedAssets.length < assetList.length) {
emittedAssets.push(...assetList);
}
const staleAssets = prevAssets.filter((asset) => { const staleAssets = prevAssets.filter((asset) => {
return assetList.includes(asset) === false; return assetList.includes(asset) === false;
}); });

View File

@ -0,0 +1,5 @@
// VIRTUAL ENTRY START
require('@nativescript/core/bundle-entry-points')
const context = require.context("~/", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/);
global.registerWebpackModules(context);
// VIRTUAL ENTRY END

View File

@ -0,0 +1,5 @@
// VIRTUAL ENTRY START
require('@nativescript/core/bundle-entry-points')
const context = require.context("~/", /* deep: */ true, /* filter: */ /\.(xml|js|(?<!\.d\.)ts|s?css)$/);
global.registerWebpackModules(context);
// VIRTUAL ENTRY END