mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Merge branch 'feat/webpack5' into pr/9133
This commit is contained in:
56
packages/webpack5/src/bin/devServer.ts
Normal file
56
packages/webpack5/src/bin/devServer.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
// import { createServer } from 'http'
|
||||
//
|
||||
// export interface IHMRStatusData {
|
||||
// seq: number
|
||||
// uuid: string,
|
||||
// hash: string
|
||||
// status: string
|
||||
// }
|
||||
//
|
||||
// export function run() {
|
||||
// createServer((req, res) => {
|
||||
// if (req.url === '/ping') {
|
||||
// console.log('PING -> PONG!')
|
||||
// return res.end("Pong.");
|
||||
// }
|
||||
//
|
||||
// if (req.method !== 'POST') {
|
||||
// res.statusCode = 400;
|
||||
// return res.end("Unsupported method.");
|
||||
// }
|
||||
//
|
||||
// let data = "";
|
||||
// req.on("data", chunk => {
|
||||
// data += chunk;
|
||||
// });
|
||||
//
|
||||
// req.on("end", () => {
|
||||
// try {
|
||||
// const signal = JSON.parse(data) as IHMRStatusData;
|
||||
// // if (!statuses[signal.hash] || statuses[signal.hash].seq < signal.seq) {
|
||||
// // statuses[signal.hash] = signal
|
||||
// // }
|
||||
// if (process.send) {
|
||||
// process.send({
|
||||
// type: 'hmr-status',
|
||||
// version: 1,
|
||||
// hash: signal.hash,
|
||||
// data: signal
|
||||
// }, (error) => {
|
||||
// if (error) {
|
||||
// console.error(`Process Send Error: `, error);
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// res.end('ok.');
|
||||
// } catch (e) {
|
||||
// res.statusCode = 400;
|
||||
// res.end("Invalid JSON.");
|
||||
// }
|
||||
// });
|
||||
// }).listen(8238)
|
||||
// }
|
||||
//
|
||||
@@ -1,11 +1,14 @@
|
||||
#!/user/bin/env node
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { redBright, green, greenBright } from 'chalk';
|
||||
import { program } from 'commander';
|
||||
import dedent from 'ts-dedent';
|
||||
import webpack from 'webpack';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import { parseEnvFlags } from '../cli/parseEnvFlags';
|
||||
|
||||
const defaultConfig = path.resolve(
|
||||
__dirname,
|
||||
'../stubs/default.config.stub.js'
|
||||
@@ -20,6 +23,8 @@ function info(message: string) {
|
||||
console.info(`${tag} ${greenBright(dedent(message))}`);
|
||||
}
|
||||
|
||||
program.enablePositionalOptions();
|
||||
|
||||
program
|
||||
.command('init')
|
||||
.description('Initialize a new webpack.config.js in the current directory.')
|
||||
@@ -35,4 +40,83 @@ program
|
||||
info('Initialized config.');
|
||||
});
|
||||
|
||||
program
|
||||
.command('build')
|
||||
.description('Build...')
|
||||
.option('--env [name]', 'environment name')
|
||||
.option('--config [path]', 'config path')
|
||||
.option('--watch', 'watch for changes')
|
||||
.allowUnknownOption()
|
||||
.action((options, command) => {
|
||||
const env = parseEnvFlags(command.args);
|
||||
// add --env <val> into the env object
|
||||
// for example if we use --env prod
|
||||
// we'd have env.env = 'prod'
|
||||
if (options.env) {
|
||||
env['env'] = options.env;
|
||||
}
|
||||
|
||||
const configPath = (() => {
|
||||
if (options.config) {
|
||||
return path.resolve(options.config);
|
||||
}
|
||||
|
||||
return path.resolve(process.cwd(), 'webpack.config.js');
|
||||
})();
|
||||
|
||||
// todo: validate config exists
|
||||
// todo: guard against invalid config
|
||||
let configuration: webpack.Configuration;
|
||||
try {
|
||||
configuration = require(configPath)(env);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
if (!configuration) {
|
||||
console.log('No configuration!');
|
||||
return;
|
||||
}
|
||||
|
||||
const compiler = webpack(configuration);
|
||||
|
||||
const webpackCompilationCallback = (
|
||||
err: webpack.WebpackError,
|
||||
stats: webpack.Stats
|
||||
) => {
|
||||
if (err) {
|
||||
// Do not keep cache anymore
|
||||
compiler.purgeInputFileSystem();
|
||||
|
||||
console.error(err.stack || err);
|
||||
if (err.details) {
|
||||
console.error(err.details);
|
||||
}
|
||||
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
console.log(
|
||||
stats.toString({
|
||||
chunks: false,
|
||||
colors: true,
|
||||
errorDetails: env.verbose,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (options.watch) {
|
||||
console.log('webpack is watching the files...');
|
||||
compiler.watch(
|
||||
configuration.watchOptions ?? {},
|
||||
webpackCompilationCallback
|
||||
);
|
||||
} else {
|
||||
compiler.run(webpackCompilationCallback);
|
||||
}
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
39
packages/webpack5/src/cli/parseEnvFlags.ts
Normal file
39
packages/webpack5/src/cli/parseEnvFlags.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { IWebpackEnv } from '@nativescript/webpack';
|
||||
|
||||
const ENV_FLAG_RE = /--env\.(\w+)(?:=(.+))?/;
|
||||
|
||||
export function parseEnvFlags(flags: string[]): IWebpackEnv {
|
||||
const envFlags = flags.filter((flag) => flag.includes('--env.'));
|
||||
|
||||
const env = {};
|
||||
|
||||
envFlags.map((flag) => {
|
||||
let [_, name, v] = ENV_FLAG_RE.exec(flag);
|
||||
let value: any = v;
|
||||
|
||||
// convert --env.foo to --env.foo=true
|
||||
if (value === undefined) {
|
||||
value = true;
|
||||
}
|
||||
|
||||
// convert true/false to boolean
|
||||
if (value === 'true' || value === 'false') {
|
||||
value = value === 'true';
|
||||
}
|
||||
|
||||
// convert numbers
|
||||
if (!isNaN(value) && !isNaN(parseFloat(value))) {
|
||||
value = +value;
|
||||
}
|
||||
|
||||
// duplicate key/name - convert to array
|
||||
if (name in env && value) {
|
||||
const orig = Array.isArray(env[name]) ? env[name] : [env[name]];
|
||||
env[name] = [...orig, value];
|
||||
} else {
|
||||
env[name] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return env;
|
||||
}
|
||||
@@ -1,19 +1,36 @@
|
||||
import { extname, resolve } from 'path';
|
||||
import Config from 'webpack-chain';
|
||||
import path from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
import { getProjectRootPath } from '../helpers/project';
|
||||
import { getProjectFilePath } from '../helpers/project';
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import { getEntryPath } from '../helpers/platform';
|
||||
import {
|
||||
getEntryDirPath,
|
||||
getEntryPath,
|
||||
getPlatformName,
|
||||
} from '../helpers/platform';
|
||||
import base from './base';
|
||||
|
||||
export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
base(config, env);
|
||||
|
||||
const tsConfigPath = path.join(getProjectRootPath(), 'tsconfig.json');
|
||||
const platform = getPlatformName();
|
||||
|
||||
const tsConfigPath = [
|
||||
getProjectFilePath('tsconfig.app.json'),
|
||||
getProjectFilePath('tsconfig.json'),
|
||||
].find((path) => existsSync(path));
|
||||
|
||||
// remove default ts rule
|
||||
config.module.rules.delete('ts');
|
||||
|
||||
// remove fork ts checked as not needed
|
||||
config.plugins.delete('ForkTsCheckerWebpackPlugin');
|
||||
|
||||
// explicitly define mainFields to make sure ngcc compiles as es2015 (module field)
|
||||
// instead of umd (main field).
|
||||
config.resolve.mainFields.add('module').add('main');
|
||||
|
||||
config.module
|
||||
.rule('angular')
|
||||
.test(/(?:\.ngfactory.js|\.ngstyle\.js|\.ts)$/)
|
||||
@@ -32,18 +49,178 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
.use('raw-loader')
|
||||
.loader('raw-loader');
|
||||
|
||||
config.plugin('AngularCompilerPlugin').use(getAngularCompilerPlugin(), [
|
||||
{
|
||||
tsConfigPath,
|
||||
mainPath: getEntryPath(),
|
||||
platformTransformers: [require('../transformers/NativeClass').default],
|
||||
},
|
||||
]);
|
||||
// exclude component css files from the normal css rule
|
||||
config.module
|
||||
.rule('css')
|
||||
.exclude // exclude *.component.{platform}.css
|
||||
.add(/\.component(\.\w+)?\.css$/);
|
||||
|
||||
// and instead use raw-loader, since that's what angular expects
|
||||
config.module
|
||||
.rule('css|component')
|
||||
.test(/\.component(\.\w+)?\.css$/)
|
||||
.use('raw-loader')
|
||||
.loader('raw-loader');
|
||||
|
||||
// get base postCSS options
|
||||
const postCSSOptions = config.module
|
||||
.rule('scss')
|
||||
.uses.get('postcss-loader')
|
||||
.get('options');
|
||||
|
||||
// exclude component css files from the normal css rule
|
||||
config.module
|
||||
.rule('scss')
|
||||
.exclude // exclude *.component.{platform}.scss
|
||||
.add(/\.component(\.\w+)?\.scss$/);
|
||||
|
||||
// and instead use raw-loader, since that's what angular expects
|
||||
config.module
|
||||
.rule('scss|component')
|
||||
.test(/\.component(\.\w+)?\.scss$/)
|
||||
.use('raw-loader')
|
||||
.loader('raw-loader')
|
||||
.end()
|
||||
.use('postcss-loader')
|
||||
.loader('postcss-loader')
|
||||
.options(postCSSOptions)
|
||||
.end()
|
||||
.use('sass-loader')
|
||||
.loader('sass-loader');
|
||||
|
||||
const angularCompilerPlugin = getAngularCompilerPlugin();
|
||||
if (angularCompilerPlugin) {
|
||||
config.plugin('AngularCompilerPlugin').use(angularCompilerPlugin, [
|
||||
{
|
||||
tsConfigPath,
|
||||
mainPath: getEntryPath(),
|
||||
// disable type checking in a forked process - it ignores
|
||||
// the hostReplacementPaths and prints errors about
|
||||
// platform suffixed files, even though they are
|
||||
// working as expected.
|
||||
forkTypeChecker: false,
|
||||
hostReplacementPaths(path: string) {
|
||||
const ext = extname(path);
|
||||
const platformExt = `.${platform}${ext}`;
|
||||
|
||||
// already includes a platform specific extension - ignore
|
||||
if (path.includes(platformExt)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const platformPath = path.replace(ext, platformExt);
|
||||
// check if the same file exists with a platform suffix and return if it does.
|
||||
if (existsSync(platformPath)) {
|
||||
// console.log(`[hostReplacementPaths] resolving "${path}" to "${platformPath}"`);
|
||||
return platformPath;
|
||||
}
|
||||
|
||||
// just return the original path otherwise
|
||||
return path;
|
||||
},
|
||||
platformTransformers: [require('../transformers/NativeClass').default],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const angularWebpackPlugin = getAngularWebpackPlugin();
|
||||
if (angularWebpackPlugin) {
|
||||
// angular no longer supports transformers.
|
||||
// so we patch their method until they do
|
||||
// https://github.com/angular/angular-cli/pull/21046
|
||||
const originalCreateFileEmitter =
|
||||
angularWebpackPlugin.prototype.createFileEmitter;
|
||||
angularWebpackPlugin.prototype.createFileEmitter = function (
|
||||
...args: any[]
|
||||
) {
|
||||
let transformers = args[1] || {};
|
||||
if (!transformers.before) {
|
||||
transformers.before = [];
|
||||
}
|
||||
transformers.before.push(require('../transformers/NativeClass').default);
|
||||
args[1] = transformers;
|
||||
return originalCreateFileEmitter.apply(this, args);
|
||||
};
|
||||
config.plugin('AngularWebpackPlugin').use(angularWebpackPlugin, [
|
||||
{
|
||||
tsconfig: tsConfigPath,
|
||||
directTemplateLoading: false,
|
||||
},
|
||||
]);
|
||||
|
||||
config.when(env.hmr, (config) => {
|
||||
config.module
|
||||
.rule('angular-hmr')
|
||||
.enforce('post')
|
||||
.test(getEntryPath())
|
||||
.use('angular-hot-loader')
|
||||
.loader('angular-hot-loader');
|
||||
});
|
||||
}
|
||||
|
||||
// look for platform specific polyfills first
|
||||
// falling back to independent polyfills
|
||||
const polyfillsPath = [
|
||||
resolve(getEntryDirPath(), `polyfills.${platform}.ts`),
|
||||
resolve(getEntryDirPath(), `polyfills.ts`),
|
||||
].find((path) => existsSync(path));
|
||||
|
||||
if (polyfillsPath) {
|
||||
const paths = config.entry('bundle').values();
|
||||
|
||||
// replace globals with the polyfills file which
|
||||
// should handle loading the correct globals
|
||||
// and any additional polyfills required.
|
||||
if (paths.includes('@nativescript/core/globals/index.js')) {
|
||||
paths[
|
||||
paths.indexOf('@nativescript/core/globals/index.js')
|
||||
] = polyfillsPath;
|
||||
|
||||
// replace paths with the update paths
|
||||
config.entry('bundle').clear().merge(paths);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter common undesirable warnings
|
||||
config.set(
|
||||
'ignoreWarnings',
|
||||
(config.get('ignoreWarnings') ?? []).concat([
|
||||
/**
|
||||
* This rule hides
|
||||
* +-----------------------------------------------------------------------------------------+
|
||||
* | WARNING in Zone.js does not support native async/await in ES2017+. |
|
||||
* | These blocks are not intercepted by zone.js and will not triggering change detection. |
|
||||
* | See: https://github.com/angular/zone.js/pull/1140 for more information. |
|
||||
* +-----------------------------------------------------------------------------------------+
|
||||
*/
|
||||
/Zone\.js does not support native async\/await/,
|
||||
/**
|
||||
* This rule hides
|
||||
* +-----------------------------------------------------------------------------------------+
|
||||
* | WARNING in environment.*.ts is part of the TypeScript compilation but it's unused. |
|
||||
* | Add only entry points to the 'files' or 'include' properties in your tsconfig. |
|
||||
* +-----------------------------------------------------------------------------------------+
|
||||
*/
|
||||
/environment(\.(\w+))?\.ts is part of the TypeScript compilation but it's unused/,
|
||||
// Additional rules to suppress warnings that are safe to ignore
|
||||
{
|
||||
module: /@angular\/core\/(__ivy_ngcc__\/)?fesm2015\/core.js/,
|
||||
message: /Critical dependency: the request of a dependency is an expression/,
|
||||
},
|
||||
/core\/profiling/,
|
||||
/core\/ui\/styling/,
|
||||
])
|
||||
);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function getAngularCompilerPlugin() {
|
||||
function getAngularCompilerPlugin(): any {
|
||||
const { AngularCompilerPlugin } = require('@ngtools/webpack');
|
||||
return AngularCompilerPlugin;
|
||||
}
|
||||
|
||||
function getAngularWebpackPlugin(): any {
|
||||
const { AngularWebpackPlugin } = require('@ngtools/webpack');
|
||||
return AngularWebpackPlugin;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import { DefinePlugin, HotModuleReplacementPlugin } from 'webpack';
|
||||
import {
|
||||
ContextExclusionPlugin,
|
||||
DefinePlugin,
|
||||
HotModuleReplacementPlugin,
|
||||
} from 'webpack';
|
||||
import Config from 'webpack-chain';
|
||||
import { resolve } from 'path';
|
||||
|
||||
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
|
||||
// import { WatchStateLoggerPlugin } from '../plugins/WatchStateLoggerPlugin';
|
||||
import { PlatformSuffixPlugin } from '../plugins/PlatformSuffixPlugin';
|
||||
import { applyFileReplacements } from '../helpers/fileReplacements';
|
||||
import { addCopyRule, applyCopyRules } from '../helpers/copyRules';
|
||||
import { WatchStatePlugin } from '../plugins/WatchStatePlugin';
|
||||
import { getValue } from '../helpers/config';
|
||||
import { projectUsesCustomFlavor } from '../helpers/flavor';
|
||||
import { getProjectRootPath } from '../helpers/project';
|
||||
import { getProjectFilePath } from '../helpers/project';
|
||||
import { hasDependency } from '../helpers/dependencies';
|
||||
import { IWebpackEnv } from '../index';
|
||||
import { applyDotEnvPlugin } from '../helpers/dotEnv';
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import { getValue } from '../helpers/config';
|
||||
import { getIPS } from '../helpers/host';
|
||||
import {
|
||||
getPlatformName,
|
||||
getAbsoluteDistPath,
|
||||
@@ -23,7 +28,7 @@ import {
|
||||
getEntryPath,
|
||||
} from '../helpers/platform';
|
||||
|
||||
export default function (config: Config, env: IWebpackEnv): Config {
|
||||
export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
const entryPath = getEntryPath();
|
||||
const platform = getPlatformName();
|
||||
const mode = env.production ? 'production' : 'development';
|
||||
@@ -40,8 +45,39 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
// resolved at runtime
|
||||
config.externals(['package.json', '~/package.json']);
|
||||
|
||||
// todo: devtool
|
||||
config.devtool('inline-source-map');
|
||||
// disable marking built-in node modules as external
|
||||
// since they are not available at runtime and
|
||||
// should be bundled (requires polyfills)
|
||||
// for example `npm i --save url` to
|
||||
// polyfill the node url module.
|
||||
config.set('externalsPresets', {
|
||||
node: false,
|
||||
});
|
||||
|
||||
const getSourceMapType = (map: string | boolean): Config.DevTool => {
|
||||
const defaultSourceMap = 'inline-source-map';
|
||||
|
||||
if (typeof map === 'undefined') {
|
||||
// source-maps disabled in production by default
|
||||
// enabled with --env.sourceMap=<type>
|
||||
if (mode === 'production') {
|
||||
// todo: we may set up SourceMapDevToolPlugin to generate external maps in production
|
||||
return false;
|
||||
}
|
||||
|
||||
return defaultSourceMap;
|
||||
}
|
||||
|
||||
// when --env.sourceMap=true is passed, use default
|
||||
if (typeof map === 'boolean' && map) {
|
||||
return defaultSourceMap;
|
||||
}
|
||||
|
||||
// pass any type of sourceMap with --env.sourceMap=<type>
|
||||
return map as Config.DevTool;
|
||||
};
|
||||
|
||||
config.devtool(getSourceMapType(env.sourceMap));
|
||||
|
||||
// todo: figure out easiest way to make "node" target work in ns
|
||||
// rather than the custom ns target implementation that's hard to maintain
|
||||
@@ -54,10 +90,20 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
.add('@nativescript/core/globals/index.js')
|
||||
.add(entryPath);
|
||||
|
||||
// Add android app components to the bundle to SBG can generate the java classes
|
||||
if (platform === 'android') {
|
||||
const appComponents = env.appComponents || [];
|
||||
appComponents.push('@nativescript/core/ui/frame');
|
||||
appComponents.push('@nativescript/core/ui/frame/activity');
|
||||
appComponents.map((component) => {
|
||||
config.entry('bundle').add(component);
|
||||
});
|
||||
}
|
||||
|
||||
// inspector_modules
|
||||
config.when(shouldIncludeInspectorModules(), (config) => {
|
||||
config
|
||||
.entry('tns_modules/@nativescript/core/inspector_modules')
|
||||
.entry('tns_modules/inspector_modules')
|
||||
.add('@nativescript/core/inspector_modules');
|
||||
});
|
||||
|
||||
@@ -66,7 +112,15 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
.pathinfo(false)
|
||||
.publicPath('')
|
||||
.libraryTarget('commonjs')
|
||||
.globalObject('global');
|
||||
.globalObject('global')
|
||||
.set('clean', true);
|
||||
|
||||
config.watchOptions({
|
||||
ignored: [
|
||||
`${getProjectFilePath('platforms')}/**`,
|
||||
`${getProjectFilePath(env.appResourcesPath ?? 'App_Resources')}/**`,
|
||||
],
|
||||
});
|
||||
|
||||
// Set up Terser options
|
||||
config.optimization.minimizer('TerserPlugin').use(TerserPlugin, [
|
||||
@@ -75,13 +129,20 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
compress: {
|
||||
collapse_vars: platform !== 'android',
|
||||
sequences: platform !== 'android',
|
||||
keep_infinity: true,
|
||||
drop_console: mode === 'production',
|
||||
global_defs: {
|
||||
__UGLIFIED__: true,
|
||||
},
|
||||
},
|
||||
// todo: move into vue.ts if not required in other flavors?
|
||||
keep_fnames: true,
|
||||
keep_classnames: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
config.optimization.runtimeChunk('single');
|
||||
|
||||
config.optimization.splitChunks({
|
||||
cacheGroups: {
|
||||
defaultVendor: {
|
||||
@@ -95,10 +156,13 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
|
||||
// look for loaders in
|
||||
// - node_modules/@nativescript/webpack/dist/loaders
|
||||
// - node_modules/@nativescript/webpack/node_modules
|
||||
// - node_modules
|
||||
// allows for cleaner rules, without having to specify full paths to loaders
|
||||
config.resolveLoader.modules
|
||||
.add('node_modules/@nativescript/webpack/dist/loaders')
|
||||
.add(resolve(__dirname, '../loaders'))
|
||||
.add(resolve(__dirname, '../../node_modules'))
|
||||
.add(getProjectFilePath('node_modules'))
|
||||
.add('node_modules');
|
||||
|
||||
config.resolve.extensions
|
||||
@@ -119,6 +183,28 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
// resolve symlinks
|
||||
config.resolve.symlinks(true);
|
||||
|
||||
// resolve modules in project node_modules first
|
||||
// then fall-back to default node resolution (up the parent folder chain)
|
||||
config.resolve.modules
|
||||
.add(getProjectFilePath('node_modules'))
|
||||
.add('node_modules');
|
||||
|
||||
config.module
|
||||
.rule('bundle')
|
||||
.enforce('post')
|
||||
.test(entryPath)
|
||||
.use('app-css-loader')
|
||||
.loader('app-css-loader')
|
||||
.options({
|
||||
platform,
|
||||
})
|
||||
.end()
|
||||
.use('nativescript-hot-loader')
|
||||
.loader('nativescript-hot-loader')
|
||||
.options({
|
||||
injectHMRRuntime: true,
|
||||
});
|
||||
|
||||
// set up ts support
|
||||
config.module
|
||||
.rule('ts')
|
||||
@@ -156,19 +242,25 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
});
|
||||
|
||||
// set up js
|
||||
// todo: do we need babel-loader? It's useful to support it
|
||||
config.module
|
||||
.rule('js')
|
||||
.test(/\.js$/)
|
||||
.exclude.add(/node_modules/)
|
||||
.end()
|
||||
.use('babel-loader')
|
||||
.loader('babel-loader')
|
||||
.options({
|
||||
generatorOpts: {
|
||||
compact: false,
|
||||
},
|
||||
});
|
||||
.end();
|
||||
|
||||
config.module
|
||||
.rule('workers')
|
||||
.test(/\.(js|ts)$/)
|
||||
.use('nativescript-worker-loader')
|
||||
.loader('nativescript-worker-loader');
|
||||
|
||||
// config.resolve.extensions.add('.xml');
|
||||
// set up xml
|
||||
config.module
|
||||
.rule('xml')
|
||||
.test(/\.xml$/)
|
||||
.use('xml-namespace-loader')
|
||||
.loader('xml-namespace-loader');
|
||||
|
||||
// default PostCSS options to use
|
||||
// projects can change settings
|
||||
@@ -213,14 +305,6 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
.use('sass-loader')
|
||||
.loader('sass-loader');
|
||||
|
||||
// items to clean
|
||||
config.plugin('CleanWebpackPlugin').use(CleanWebpackPlugin, [
|
||||
{
|
||||
cleanOnceBeforeBuildPatterns: [`${getAbsoluteDistPath()}/**/*`],
|
||||
verbose: !!env.verbose,
|
||||
},
|
||||
]);
|
||||
|
||||
// config.plugin('NormalModuleReplacementPlugin').use(NormalModuleReplacementPlugin, [
|
||||
// /.*/,
|
||||
// request => {
|
||||
@@ -237,6 +321,28 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
},
|
||||
]);
|
||||
|
||||
// Makes sure that require.context will never include
|
||||
// App_Resources, regardless where they are located.
|
||||
config
|
||||
.plugin('ContextExclusionPlugin|App_Resources')
|
||||
.use(ContextExclusionPlugin, [new RegExp(`(.*)App_Resources(.*)`)]);
|
||||
|
||||
// Filter common undesirable warnings
|
||||
config.set(
|
||||
'ignoreWarnings',
|
||||
(config.get('ignoreWarnings') ?? []).concat([
|
||||
/**
|
||||
* This rule hides
|
||||
* +-----------------------------------------------------------------------------------------+
|
||||
* | WARNING in ./node_modules/@angular/core/fesm2015/core.js 29714:15-102 |
|
||||
* | System.import() is deprecated and will be removed soon. Use import() instead. |
|
||||
* | For more info visit https://webpack.js.org/guides/code-splitting/ |
|
||||
* +-----------------------------------------------------------------------------------------+
|
||||
*/
|
||||
/System.import\(\) is deprecated/,
|
||||
])
|
||||
);
|
||||
|
||||
// todo: refine defaults
|
||||
config.plugin('DefinePlugin').use(DefinePlugin, [
|
||||
{
|
||||
@@ -244,7 +350,10 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
__NS_WEBPACK__: true,
|
||||
__UI_USE_XML_PARSER__: true,
|
||||
__UI_USE_EXTERNAL_RENDERER__: projectUsesCustomFlavor(),
|
||||
__CSS_PARSER__: JSON.stringify(getValue('cssParser')), // todo: replace from config value
|
||||
__NS_ENV_VERBOSE__: !!env.verbose,
|
||||
__NS_DEV_HOST_IPS__:
|
||||
mode === 'development' ? JSON.stringify(getIPS()) : `[]`,
|
||||
__CSS_PARSER__: JSON.stringify(getValue('cssParser', 'css-tree')),
|
||||
__ANDROID__: platform === 'android',
|
||||
__IOS__: platform === 'ios',
|
||||
/* for compat only */ 'global.isAndroid': platform === 'android',
|
||||
@@ -256,6 +365,12 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
},
|
||||
]);
|
||||
|
||||
// enable DotEnv
|
||||
applyDotEnvPlugin(config);
|
||||
|
||||
// replacements
|
||||
applyFileReplacements(config);
|
||||
|
||||
// set up default copy rules
|
||||
addCopyRule('assets/**');
|
||||
addCopyRule('fonts/**');
|
||||
@@ -263,8 +378,6 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
|
||||
applyCopyRules(config);
|
||||
|
||||
// add the WatchStateLogger plugin used to notify the CLI of build state
|
||||
// config.plugin('WatchStateLoggerPlugin').use(WatchStateLoggerPlugin);
|
||||
config.plugin('WatchStatePlugin').use(WatchStatePlugin);
|
||||
|
||||
config.when(env.hmr, (config) => {
|
||||
@@ -272,14 +385,13 @@ export default function (config: Config, env: IWebpackEnv): Config {
|
||||
});
|
||||
|
||||
config.when(env.report, (config) => {
|
||||
const projectRoot = getProjectRootPath();
|
||||
config.plugin('BundleAnalyzerPlugin').use(BundleAnalyzerPlugin, [
|
||||
{
|
||||
analyzerMode: 'static',
|
||||
generateStatsFile: true,
|
||||
openAnalyzer: false,
|
||||
reportFilename: resolve(projectRoot, 'report', 'report.html'),
|
||||
statsFilename: resolve(projectRoot, 'report', 'stats.json'),
|
||||
reportFilename: getProjectFilePath('report/report.html'),
|
||||
statsFilename: getProjectFilePath('report/stats.json'),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1,45 +1,42 @@
|
||||
import VirtualModulesPlugin from 'webpack-virtual-modules';
|
||||
import { ContextExclusionPlugin } from 'webpack';
|
||||
import Config from 'webpack-chain';
|
||||
import dedent from 'ts-dedent';
|
||||
import { join } from 'path';
|
||||
|
||||
import { getEntryDirPath } from '../helpers/platform';
|
||||
import { getEntryPath, getEntryDirPath } from '../helpers/platform';
|
||||
import { addVirtualEntry } from '../helpers/virtualModules';
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import base from './base';
|
||||
|
||||
export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
base(config, env);
|
||||
|
||||
const virtualEntryPath = join(getEntryDirPath(), '__virtual_entry__.js');
|
||||
const entryPath = getEntryPath();
|
||||
const filterRE = '/.(xml|js|s?css)$/';
|
||||
const virtualEntryPath = addVirtualEntry(
|
||||
config,
|
||||
'javascript',
|
||||
`
|
||||
// VIRTUAL ENTRY START
|
||||
require('@nativescript/core/bundle-entry-points')
|
||||
const context = require.context("~/", /* deep: */ true, /* filter: */ ${filterRE});
|
||||
global.registerWebpackModules(context);
|
||||
// VIRTUAL ENTRY END
|
||||
`
|
||||
);
|
||||
|
||||
config.entry('bundle').add(virtualEntryPath);
|
||||
|
||||
config
|
||||
.plugin('ContextExclusionPluginPlugin')
|
||||
.use(ContextExclusionPlugin, [/__virtual_entry__\.js$/]);
|
||||
// config.resolve.extensions.add('.xml');
|
||||
|
||||
// Add a virtual entry module that will register all modules into
|
||||
// the nativescript module loader/handler
|
||||
config.plugin('VirtualModulesPlugin').use(VirtualModulesPlugin, [
|
||||
{
|
||||
[virtualEntryPath]: dedent`
|
||||
require('@nativescript/core/bundle-entry-points')
|
||||
const context = require.context("~/", /* deep: */ true, /* filter: */ ${filterRE});
|
||||
global.registerWebpackModules(context);
|
||||
`,
|
||||
},
|
||||
]);
|
||||
|
||||
config.resolve.extensions.add('.xml');
|
||||
|
||||
// set up xml
|
||||
// set up core HMR
|
||||
config.module
|
||||
.rule('xml')
|
||||
.test(/\.xml$/)
|
||||
.use('xml-namespace-loader')
|
||||
.loader('xml-namespace-loader');
|
||||
.rule('hmr-core')
|
||||
.test(/\.js$/)
|
||||
.exclude.add(/node_modules/)
|
||||
.add(entryPath)
|
||||
.end()
|
||||
.use('nativescript-hot-loader')
|
||||
.loader('nativescript-hot-loader')
|
||||
.options({
|
||||
appPath: getEntryDirPath(),
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { merge } from 'webpack-merge';
|
||||
import Config from 'webpack-chain';
|
||||
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import { getPlatformName } from '../helpers/platform';
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import base from './base';
|
||||
|
||||
export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Config from 'webpack-chain';
|
||||
|
||||
import { getProjectRootPath } from '../helpers/project';
|
||||
import { getProjectFilePath, getProjectRootPath } from '../helpers/project';
|
||||
import { getPlatformName } from '../helpers/platform';
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import { error } from '../helpers/log';
|
||||
@@ -52,15 +52,14 @@ function getSvelteConfigPreprocessor(): any {
|
||||
return config?.preprocess;
|
||||
}
|
||||
|
||||
function getSvelteConfig(): { preprocess: any } | undefined {
|
||||
interface ISvelteConfig {
|
||||
preprocess: any;
|
||||
}
|
||||
|
||||
function getSvelteConfig(): ISvelteConfig | undefined {
|
||||
try {
|
||||
const resolvedPath = require.resolve(`./svelte.config.js`, {
|
||||
paths: [getProjectRootPath()],
|
||||
});
|
||||
return require(resolvedPath);
|
||||
return require(getProjectFilePath('svelte.config.js')) as ISvelteConfig;
|
||||
} catch (err) {
|
||||
// todo: remove when jest supports mocking require.resolve
|
||||
if (__TEST__) return;
|
||||
error('Could not find svelte.config.js.', err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,42 @@
|
||||
import Config from 'webpack-chain';
|
||||
|
||||
import { IWebpackEnv } from '../index';
|
||||
import { getEntryDirPath, getEntryPath } from '../helpers/platform';
|
||||
import { addVirtualEntry } from '../helpers/virtualModules';
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import base from './base';
|
||||
|
||||
// todo: add base configuration for core
|
||||
export default function (config: Config, env: IWebpackEnv): Config {
|
||||
export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
base(config, env);
|
||||
const entryPath = getEntryPath();
|
||||
const filterRE = '/\\.(xml|js|(?<!\\.d\\.)ts|s?css)$/';
|
||||
const virtualEntryPath = addVirtualEntry(
|
||||
config,
|
||||
'typescript',
|
||||
`
|
||||
// VIRTUAL ENTRY START
|
||||
require('@nativescript/core/bundle-entry-points')
|
||||
const context = require.context("~/", /* deep: */ true, /* filter: */ ${filterRE});
|
||||
global.registerWebpackModules(context);
|
||||
// VIRTUAL ENTRY END
|
||||
`
|
||||
);
|
||||
|
||||
config.entry('bundle').add(virtualEntryPath);
|
||||
|
||||
// config.resolve.extensions.add('.xml');
|
||||
|
||||
// set up core HMR
|
||||
config.module
|
||||
.rule('hmr-core')
|
||||
.test(/\.(js|ts)$/)
|
||||
.exclude.add(/node_modules/)
|
||||
.add(entryPath)
|
||||
.end()
|
||||
.use('nativescript-hot-loader')
|
||||
.loader('nativescript-hot-loader')
|
||||
.options({
|
||||
appPath: getEntryDirPath(),
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { VueLoaderPlugin } from 'vue-loader';
|
||||
import { merge } from 'webpack-merge';
|
||||
import Config from 'webpack-chain';
|
||||
import fs from 'fs';
|
||||
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import { hasDependency } from '../helpers/dependencies';
|
||||
import { getPlatformName } from '../helpers/platform';
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import { error } from '../helpers/log';
|
||||
import base from './base';
|
||||
|
||||
export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
@@ -11,6 +14,11 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
|
||||
const platform = getPlatformName();
|
||||
|
||||
// we need to patch VueLoader if we want to enable hmr
|
||||
if (env.hmr) {
|
||||
patchVueLoaderForHMR();
|
||||
}
|
||||
|
||||
// resolve .vue files
|
||||
// the order is reversed because we are using prepend!
|
||||
config.resolve.extensions.prepend('.vue').prepend(`.${platform}.vue`);
|
||||
@@ -28,6 +36,21 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
};
|
||||
});
|
||||
|
||||
// apply vue stylePostLoader to inject component scope into the css
|
||||
// this would usually be automatic, however in NS we don't use the
|
||||
// css-loader, so VueLoader doesn't inject the rule at all.
|
||||
config.module
|
||||
.rule('css')
|
||||
.use('vue-css-loader')
|
||||
.after('css2json-loader')
|
||||
.loader('vue-loader/lib/loaders/stylePostLoader.js');
|
||||
|
||||
config.module
|
||||
.rule('scss')
|
||||
.use('vue-css-loader')
|
||||
.after('css2json-loader')
|
||||
.loader('vue-loader/lib/loaders/stylePostLoader.js');
|
||||
|
||||
// set up ts support in vue files
|
||||
config.module
|
||||
.rule('ts')
|
||||
@@ -39,18 +62,20 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
});
|
||||
});
|
||||
|
||||
config.plugin('ForkTsCheckerWebpackPlugin').tap((args) => {
|
||||
args[0] = merge(args[0], {
|
||||
typescript: {
|
||||
extensions: {
|
||||
vue: {
|
||||
enabled: true,
|
||||
compiler: 'nativescript-vue-template-compiler',
|
||||
config.when(hasDependency('typescript'), (config) => {
|
||||
config.plugin('ForkTsCheckerWebpackPlugin').tap((args) => {
|
||||
args[0] = merge(args[0], {
|
||||
typescript: {
|
||||
extensions: {
|
||||
vue: {
|
||||
enabled: true,
|
||||
compiler: 'nativescript-vue-template-compiler',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return args;
|
||||
});
|
||||
return args;
|
||||
});
|
||||
|
||||
// add VueLoaderPlugin as the first plugin
|
||||
@@ -65,3 +90,22 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches source of vue-loader to set the isServer flag to false
|
||||
* so hmr gets enabled.
|
||||
*/
|
||||
function patchVueLoaderForHMR() {
|
||||
try {
|
||||
const vueLoaderPath = require.resolve('vue-loader/lib/index.js');
|
||||
const source = fs.readFileSync(vueLoaderPath).toString();
|
||||
const patchedSource = source.replace(
|
||||
/(isServer\s=\s)(target\s===\s'node')/g,
|
||||
'$1false;'
|
||||
);
|
||||
fs.writeFileSync(vueLoaderPath, patchedSource);
|
||||
delete require.cache[vueLoaderPath];
|
||||
} catch (err) {
|
||||
error('Failed to patch VueLoader - HMR may not work properly!');
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/webpack5/src/globals.d.ts
vendored
2
packages/webpack5/src/globals.d.ts
vendored
@@ -1 +1 @@
|
||||
declare var __TEST__: boolean;
|
||||
// define globals here
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { env } from '../index';
|
||||
import { error } from './log';
|
||||
import { error, warnOnce } from './log';
|
||||
|
||||
function getCLILib() {
|
||||
if (!env.nativescriptLibPath) {
|
||||
throw error(`
|
||||
warnOnce(
|
||||
'getCLILib',
|
||||
`
|
||||
Cannot find NativeScript CLI path. Make sure --env.nativescriptLibPath is passed
|
||||
`);
|
||||
`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return require(env.nativescriptLibPath);
|
||||
@@ -15,11 +19,16 @@ function getCLILib() {
|
||||
* Utility to get a value from the nativescript.config.ts file.
|
||||
*
|
||||
* @param {string} key The key to get from the config. Supports dot-notation.
|
||||
* @param defaultValue The fallback value if the key is not set in the config.
|
||||
*/
|
||||
export function getValue<T = any>(key: string): T {
|
||||
export function getValue<T = any>(key: string, defaultValue?: any): T {
|
||||
const lib = getCLILib();
|
||||
|
||||
return (lib.projectConfigService as { getValue(key: string): T }).getValue(
|
||||
key
|
||||
);
|
||||
if (!lib) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return (lib.projectConfigService as {
|
||||
getValue(key: string, defaultValue?: any): T;
|
||||
}).getValue(key, defaultValue);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||
import { relative, resolve } from 'path';
|
||||
import { basename, relative, resolve } from 'path';
|
||||
import Config from 'webpack-chain';
|
||||
|
||||
import { getProjectRootPath } from './project';
|
||||
@@ -12,17 +12,29 @@ import { env } from '..';
|
||||
export let copyRules = new Set([]);
|
||||
|
||||
/**
|
||||
* Utility to add new copy rules. Accepts a glob. For example
|
||||
* @internal
|
||||
*/
|
||||
export let additionalCopyRules = [];
|
||||
|
||||
/**
|
||||
* Utility to add new copy rules. Accepts a glob or an object. For example
|
||||
* - **\/*.html - copy all .html files found in any sub dir.
|
||||
* - myFolder/* - copy all files from myFolder
|
||||
*
|
||||
* When passing an object - no additional processing is done, and it's
|
||||
* applied as-is. Make sure to set every required property.
|
||||
*
|
||||
* The path is relative to the folder of the entry file
|
||||
* (specified in the main field of the package.json)
|
||||
*
|
||||
* @param {string} glob
|
||||
* @param {string|object} globOrObject
|
||||
*/
|
||||
export function addCopyRule(glob: string) {
|
||||
copyRules.add(glob);
|
||||
export function addCopyRule(globOrObject: string | object) {
|
||||
if (typeof globOrObject === 'string') {
|
||||
return copyRules.add(globOrObject);
|
||||
}
|
||||
|
||||
additionalCopyRules.push(globOrObject);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,29 +53,30 @@ export function removeCopyRule(glob: string) {
|
||||
*/
|
||||
export function applyCopyRules(config: Config) {
|
||||
const entryDir = getEntryDirPath();
|
||||
// todo: handle empty appResourcesPath?
|
||||
// (the CLI should always pass the path - maybe not required)
|
||||
const appResourcesFullPath = resolve(
|
||||
getProjectRootPath(),
|
||||
env.appResourcesPath
|
||||
);
|
||||
|
||||
const globOptions = {
|
||||
dot: false,
|
||||
ignore: [
|
||||
// ignore everything in App_Resources (regardless where they are located)
|
||||
`${relative(entryDir, appResourcesFullPath)}/**`,
|
||||
],
|
||||
ignore: [],
|
||||
};
|
||||
|
||||
// todo: do we need to handle empty appResourcesPath?
|
||||
// (the CLI should always pass the path - maybe not required)
|
||||
if (env.appResourcesPath) {
|
||||
const appResourcesFolderName = basename(env.appResourcesPath);
|
||||
|
||||
// ignore everything in App_Resources (regardless where they are located)
|
||||
globOptions.ignore.push(`**/${appResourcesFolderName}/**`);
|
||||
}
|
||||
|
||||
config.plugin('CopyWebpackPlugin').use(CopyWebpackPlugin, [
|
||||
{
|
||||
patterns: Array.from(copyRules).map((glob) => ({
|
||||
from: glob,
|
||||
context: entryDir,
|
||||
noErrorOnMissing: true,
|
||||
globOptions,
|
||||
})),
|
||||
patterns: Array.from(copyRules)
|
||||
.map((glob) => ({
|
||||
from: glob,
|
||||
context: entryDir,
|
||||
noErrorOnMissing: true,
|
||||
globOptions,
|
||||
}))
|
||||
.concat(additionalCopyRules),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getPackageJson, getProjectRootPath } from './project';
|
||||
import path from 'path';
|
||||
|
||||
import { getPackageJson, getProjectRootPath } from './project';
|
||||
|
||||
// todo: memoize
|
||||
/**
|
||||
* Utility to get all dependencies from the project package.json.
|
||||
|
||||
49
packages/webpack5/src/helpers/dotEnv.ts
Normal file
49
packages/webpack5/src/helpers/dotEnv.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import DotEnvPlugin from 'dotenv-webpack';
|
||||
import Config from 'webpack-chain';
|
||||
import { existsSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
import { getProjectRootPath } from './project';
|
||||
import { env } from '..';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function applyDotEnvPlugin(config: Config) {
|
||||
const path = getDotEnvPath();
|
||||
|
||||
config.when(path !== null, (config) => {
|
||||
config.plugin('DotEnvPlugin').use(DotEnvPlugin, [
|
||||
{
|
||||
path,
|
||||
silent: true, // hide any errors
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
function getDotEnvFileName(): string {
|
||||
if (env.env) {
|
||||
return `.env.${env.env}`;
|
||||
}
|
||||
|
||||
return '.env';
|
||||
}
|
||||
|
||||
function getDotEnvPath(): string {
|
||||
const dotEnvPath = resolve(getProjectRootPath(), '.env');
|
||||
const dotEnvWithEnvPath = resolve(getProjectRootPath(), getDotEnvFileName());
|
||||
|
||||
// look for .env.<env>
|
||||
if (existsSync(dotEnvWithEnvPath)) {
|
||||
return dotEnvWithEnvPath;
|
||||
}
|
||||
|
||||
// fall back to .env
|
||||
if (existsSync(dotEnvPath)) {
|
||||
return dotEnvPath;
|
||||
}
|
||||
|
||||
// don't use .env
|
||||
return null;
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import { getAllDependencies, getDependencyPath } from './dependencies';
|
||||
import { clearCurrentPlugin, setCurrentPlugin } from '../index';
|
||||
import { info, warn } from './log';
|
||||
import * as lib from '../index';
|
||||
import { clearCurrentPlugin, setCurrentPlugin } from '../index';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
||||
68
packages/webpack5/src/helpers/fileReplacements.ts
Normal file
68
packages/webpack5/src/helpers/fileReplacements.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { resolve } from 'path';
|
||||
|
||||
import { env as _env, IWebpackEnv } from '../index';
|
||||
import { addCopyRule } from './copyRules';
|
||||
import { getProjectRootPath } from './project';
|
||||
|
||||
interface IReplacementMap {
|
||||
[_replace: string]: /* _with */ string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function getFileReplacementsFromEnv(
|
||||
env: IWebpackEnv = _env
|
||||
): IReplacementMap {
|
||||
const fileReplacements: IReplacementMap = {};
|
||||
|
||||
const entries: string[] = (() => {
|
||||
if (Array.isArray(env.replace)) {
|
||||
return env.replace;
|
||||
}
|
||||
|
||||
if (typeof env.replace === 'string') {
|
||||
return [env.replace];
|
||||
}
|
||||
|
||||
return [];
|
||||
})();
|
||||
|
||||
entries.forEach((replaceEntry) => {
|
||||
replaceEntry.split(/,\s*/).forEach((r: string) => {
|
||||
let [_replace, _with] = r.split(':');
|
||||
|
||||
if (!_replace || !_with) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure to resolve replacements to a full path
|
||||
// relative to the project root
|
||||
_replace = resolve(getProjectRootPath(), _replace);
|
||||
_with = resolve(getProjectRootPath(), _with);
|
||||
|
||||
fileReplacements[_replace] = _with;
|
||||
});
|
||||
});
|
||||
|
||||
return fileReplacements;
|
||||
}
|
||||
|
||||
export function applyFileReplacements(
|
||||
config,
|
||||
fileReplacements: IReplacementMap = getFileReplacementsFromEnv()
|
||||
) {
|
||||
Object.entries(fileReplacements).forEach(([_replace, _with]) => {
|
||||
// in case we are replacing source files - we'll use aliases
|
||||
if (_replace.match(/\.(ts|js)$/)) {
|
||||
return config.resolve.alias.set(_replace, _with);
|
||||
}
|
||||
|
||||
// otherwise we will override the replaced file with the replacement
|
||||
addCopyRule({
|
||||
from: _with, // copy the replacement file
|
||||
to: _replace, // to the original "to-be-replaced" file
|
||||
force: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
13
packages/webpack5/src/helpers/host.ts
Normal file
13
packages/webpack5/src/helpers/host.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import os from 'os';
|
||||
|
||||
export function getIPS() {
|
||||
const interfaces = os.networkInterfaces();
|
||||
return Object.keys(interfaces)
|
||||
.map((name) => {
|
||||
return interfaces[name].filter(
|
||||
(binding: any) => binding.family === 'IPv4'
|
||||
)[0];
|
||||
})
|
||||
.filter(Boolean)
|
||||
.map((binding) => binding.address);
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import {
|
||||
getPackageJson,
|
||||
getProjectRootPath,
|
||||
getProjectFilePath,
|
||||
} from './project';
|
||||
import { addVirtualEntry, addVirtualModule } from './virtualModules';
|
||||
import { applyFileReplacements } from './fileReplacements';
|
||||
import { addCopyRule, removeCopyRule } from './copyRules';
|
||||
import { error, info, warn, warnOnce } from './log';
|
||||
import { determineProjectFlavor, projectUsesCustomFlavor } from './flavor';
|
||||
import { error, info, warn } from './log';
|
||||
import { getValue } from './config';
|
||||
import { getIPS } from './host';
|
||||
import {
|
||||
getAllDependencies,
|
||||
hasDependency,
|
||||
getDependencyPath,
|
||||
} from './dependencies';
|
||||
import { getPackageJson, getProjectRootPath } from './project';
|
||||
import {
|
||||
addPlatform,
|
||||
getAbsoluteDistPath,
|
||||
@@ -29,6 +36,7 @@ export default {
|
||||
merge,
|
||||
addCopyRule,
|
||||
removeCopyRule,
|
||||
applyFileReplacements,
|
||||
config: {
|
||||
getValue,
|
||||
},
|
||||
@@ -39,16 +47,16 @@ export default {
|
||||
},
|
||||
flavor: {
|
||||
determineProjectFlavor,
|
||||
projectUsesCustomFlavor
|
||||
projectUsesCustomFlavor,
|
||||
},
|
||||
host: {
|
||||
getIPS,
|
||||
},
|
||||
log: {
|
||||
error,
|
||||
info,
|
||||
warn,
|
||||
},
|
||||
project: {
|
||||
getProjectRootPath,
|
||||
getPackageJson,
|
||||
warnOnce,
|
||||
},
|
||||
platform: {
|
||||
addPlatform,
|
||||
@@ -59,4 +67,13 @@ export default {
|
||||
getPlatform,
|
||||
getPlatformName,
|
||||
},
|
||||
project: {
|
||||
getProjectFilePath,
|
||||
getProjectRootPath,
|
||||
getPackageJson,
|
||||
},
|
||||
virtualModules: {
|
||||
addVirtualEntry,
|
||||
addVirtualModule,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import dedent from 'ts-dedent';
|
||||
import { env } from '@nativescript/webpack';
|
||||
|
||||
// de-indents strings so multi-line string literals can be used
|
||||
function cleanup(data: any[]) {
|
||||
@@ -27,8 +28,20 @@ export function warn(...data: any): void {
|
||||
console.warn(`[@nativescript/webpack] Warn: \n`, ...cleanup(data));
|
||||
}
|
||||
|
||||
const warnedMap: any = {};
|
||||
export function warnOnce(key: string, ...data: any): void {
|
||||
if (warnedMap[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
warnedMap[key] = true;
|
||||
warn(...data);
|
||||
}
|
||||
|
||||
export function info(...data: any): void {
|
||||
console.log(`[@nativescript/webpack] Info: \n`, ...cleanup(data));
|
||||
if (env.verbose) {
|
||||
console.log(`[@nativescript/webpack] Info: \n`, ...cleanup(data));
|
||||
}
|
||||
}
|
||||
|
||||
// todo: refine
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
import { getPackageJson, getProjectRootPath } from './project';
|
||||
import { error } from './log';
|
||||
import { error, info, warnOnce } from './log';
|
||||
import { env } from '../';
|
||||
|
||||
import AndroidPlatform from '../platforms/android';
|
||||
@@ -29,7 +29,7 @@ const platforms: {
|
||||
* @param platform A platform definition of the platform specifics
|
||||
*/
|
||||
export function addPlatform(name: string, platform: INativeScriptPlatform) {
|
||||
console.log('adding platform', name, platform);
|
||||
info(`Adding platform ${name}`, platform);
|
||||
platforms[name] = platform;
|
||||
}
|
||||
|
||||
@@ -65,13 +65,20 @@ export function getPlatformName(): Platform {
|
||||
`);
|
||||
}
|
||||
|
||||
throw error(`
|
||||
warnOnce(
|
||||
'getPlatformName',
|
||||
`
|
||||
You need to provide a target platform!
|
||||
|
||||
Available platforms: ${Object.keys(platforms).join(', ')}
|
||||
|
||||
Use --env=platform=<platform> or --env=android, --env=ios to specify the target platform.
|
||||
`);
|
||||
Use --env.platform=<platform> or --env.android, --env.ios to specify the target platform.
|
||||
|
||||
Defaulting to "ios".
|
||||
`
|
||||
);
|
||||
|
||||
return 'ios';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,32 @@ interface IPackageJson {
|
||||
* Utility function to get the contents of the project package.json
|
||||
*/
|
||||
export function getPackageJson() {
|
||||
const packageJsonPath = resolve(getProjectRootPath(), 'package.json');
|
||||
|
||||
return require(packageJsonPath) as IPackageJson;
|
||||
return require(getProjectFilePath('package.json')) as IPackageJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to get project files relative to the project root.
|
||||
* @param filePath path to get
|
||||
*/
|
||||
export function getProjectFilePath(filePath: string): string {
|
||||
return resolve(getProjectRootPath(), filePath);
|
||||
}
|
||||
|
||||
// unused helper, but keeping it here as we may need it
|
||||
// todo: remove if unused for next few releases
|
||||
// function findFile(fileName, currentDir): string | null {
|
||||
// // console.log(`findFile(${fileName}, ${currentDir})`)
|
||||
// const path = resolve(currentDir, fileName);
|
||||
//
|
||||
// if (existsSync(path)) {
|
||||
// return path;
|
||||
// }
|
||||
//
|
||||
// // bail if we reached the root dir
|
||||
// if (currentDir === resolve('/')) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// // traverse to the parent folder
|
||||
// return findFile(fileName, resolve(currentDir, '..'));
|
||||
// }
|
||||
|
||||
65
packages/webpack5/src/helpers/virtualModules.ts
Normal file
65
packages/webpack5/src/helpers/virtualModules.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { ContextExclusionPlugin } from 'webpack';
|
||||
import Config from 'webpack-chain';
|
||||
import { dirname, join } from 'path';
|
||||
import { writeFileSync, mkdirSync } from 'fs';
|
||||
|
||||
import VirtualModulesPlugin from 'webpack-virtual-modules';
|
||||
import { getEntryDirPath } from './platform';
|
||||
import dedent from 'ts-dedent';
|
||||
import { getProjectFilePath } from './project';
|
||||
|
||||
export function addVirtualEntry(
|
||||
config: Config,
|
||||
name: string,
|
||||
contents: string
|
||||
): string {
|
||||
return addVirtualModule(
|
||||
config,
|
||||
`__@nativescript_webpack_virtual_entry_${name}__`,
|
||||
contents
|
||||
);
|
||||
}
|
||||
|
||||
export function addVirtualModule(
|
||||
config: Config,
|
||||
name: string,
|
||||
contents: string
|
||||
): string {
|
||||
const virtualEntryPath = join(getEntryDirPath(), `${name}`);
|
||||
|
||||
// add the virtual entry to the context exclusions
|
||||
// makes sure that require.context will never
|
||||
// include the virtual entry.
|
||||
config
|
||||
.plugin(`ContextExclusionPlugin|${name}`)
|
||||
.use(ContextExclusionPlugin, [new RegExp(`${name}\.js$`)]);
|
||||
|
||||
const options = {
|
||||
[virtualEntryPath]: dedent(contents),
|
||||
};
|
||||
|
||||
// AngularCompilerPlugin does not support virtual modules
|
||||
// https://github.com/sysgears/webpack-virtual-modules/issues/96
|
||||
// This is only an issue on v11, which has experimental webpack 5 support
|
||||
// AngularCompilerPlugin gets replaced by AngularWebpackPlugin on v12
|
||||
// todo: we can remove this special handling once we no longer support v11
|
||||
if (config.plugins.has('AngularCompilerPlugin')) {
|
||||
const compatEntryPath = getProjectFilePath(
|
||||
join('node_modules', '.nativescript', `${name}`)
|
||||
);
|
||||
mkdirSync(dirname(compatEntryPath), { recursive: true });
|
||||
writeFileSync(compatEntryPath, options[virtualEntryPath]);
|
||||
return compatEntryPath;
|
||||
}
|
||||
|
||||
if (config.plugins.has('VirtualModulesPlugin')) {
|
||||
config.plugin('VirtualModulesPlugin').tap((args) => {
|
||||
Object.assign(args[0], options);
|
||||
return args;
|
||||
});
|
||||
} else {
|
||||
config.plugin('VirtualModulesPlugin').use(VirtualModulesPlugin, [options]);
|
||||
}
|
||||
|
||||
return virtualEntryPath;
|
||||
}
|
||||
@@ -1,3 +1,13 @@
|
||||
// Make sure the Acorn Parser (used by Webpack) can parse ES-Stage3 code
|
||||
// This must be at the top BEFORE webpack is loaded so that we can extend
|
||||
// and replace the parser before webpack uses it
|
||||
// Based on the issue: https://github.com/webpack/webpack/issues/10216
|
||||
import stage3 from 'acorn-stage3';
|
||||
|
||||
// we use require to be able to override the exports
|
||||
const acorn = require('acorn');
|
||||
acorn.Parser = acorn.Parser.extend(stage3);
|
||||
|
||||
import { highlight } from 'cli-highlight';
|
||||
import { merge } from 'webpack-merge';
|
||||
import Config from 'webpack-chain';
|
||||
@@ -12,8 +22,11 @@ import helpers from './helpers';
|
||||
export interface IWebpackEnv {
|
||||
[name: string]: any;
|
||||
|
||||
env?: string;
|
||||
|
||||
appPath?: string;
|
||||
appResourcesPath?: string;
|
||||
appComponents?: string[];
|
||||
|
||||
nativescriptLibPath?: string;
|
||||
|
||||
@@ -22,13 +35,16 @@ export interface IWebpackEnv {
|
||||
// for custom platforms
|
||||
platform?: string;
|
||||
|
||||
sourceMap?: string | boolean;
|
||||
production?: boolean;
|
||||
report?: boolean;
|
||||
hmr?: boolean;
|
||||
|
||||
// enable verbose output
|
||||
verbose?: boolean;
|
||||
// todo: add others
|
||||
|
||||
// misc
|
||||
replace?: string[] | string;
|
||||
}
|
||||
|
||||
interface IChainEntry {
|
||||
|
||||
117
packages/webpack5/src/loaders/angular-hot-loader/hmr-accept.ts
Normal file
117
packages/webpack5/src/loaders/angular-hot-loader/hmr-accept.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
isDevMode,
|
||||
ɵresetCompiledComponents,
|
||||
// @ts-ignore
|
||||
} from '@angular/core';
|
||||
|
||||
declare const __webpack_require__: any;
|
||||
declare const ng: any;
|
||||
|
||||
export default function (mod: any): void {
|
||||
if (!mod['hot']) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDevMode()) {
|
||||
console.error(
|
||||
`[NG HMR] Cannot use HMR when Angular is running in production mode. To prevent production mode, do not call 'enableProdMode()'.`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mod['hot'].accept();
|
||||
mod['hot'].dispose(() => {
|
||||
if (typeof ng === 'undefined') {
|
||||
console.warn(
|
||||
`[NG HMR] Cannot find global 'ng'. Likely this is caused because scripts optimization is enabled.`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ng.getInjector) {
|
||||
// View Engine
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset JIT compiled components cache
|
||||
ɵresetCompiledComponents();
|
||||
try {
|
||||
if (global['__cleanup_ng_hot__']) global['__cleanup_ng_hot__']();
|
||||
} catch (e) {
|
||||
console.error('[NG HMR] Error disposing previous module');
|
||||
console.error(e, e?.stack);
|
||||
// HMR breaks when rejecting the main module dispose, so we manually trigger an HMR restart
|
||||
const hash = __webpack_require__.h();
|
||||
console.log(`[HMR][${hash}] failure | Error disposing previous module`);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: maybe restore form values!
|
||||
// function restoreFormValues(oldInputs: any[], oldOptions: any[]): void {
|
||||
// // Restore input that are not hidden
|
||||
// const newInputs = document.querySelectorAll('input:not([type="hidden"]), textarea');
|
||||
// if (newInputs.length && newInputs.length === oldInputs.length) {
|
||||
// console.log('[NG HMR] Restoring input/textarea values.');
|
||||
// for (let index = 0; index < newInputs.length; index++) {
|
||||
// const newElement = newInputs[index];
|
||||
// const oldElement = oldInputs[index];
|
||||
|
||||
// switch (oldElement.type) {
|
||||
// case 'button':
|
||||
// case 'image':
|
||||
// case 'submit':
|
||||
// case 'reset':
|
||||
// // These types don't need any value change.
|
||||
// continue;
|
||||
// case 'radio':
|
||||
// case 'checkbox':
|
||||
// newElement.checked = oldElement.checked;
|
||||
// break;
|
||||
// case 'color':
|
||||
// case 'date':
|
||||
// case 'datetime-local':
|
||||
// case 'email':
|
||||
// case 'file':
|
||||
// case 'hidden':
|
||||
// case 'month':
|
||||
// case 'number':
|
||||
// case 'password':
|
||||
// case 'range':
|
||||
// case 'search':
|
||||
// case 'tel':
|
||||
// case 'text':
|
||||
// case 'textarea':
|
||||
// case 'time':
|
||||
// case 'url':
|
||||
// case 'week':
|
||||
// newElement.value = oldElement.value;
|
||||
// break;
|
||||
// default:
|
||||
// console.warn('[NG HMR] Unknown input type ' + oldElement.type + '.');
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// dispatchEvents(newElement);
|
||||
// }
|
||||
// } else if (oldInputs.length) {
|
||||
// console.warn('[NG HMR] Cannot restore input/textarea values.');
|
||||
// }
|
||||
|
||||
// // Restore option
|
||||
// const newOptions = document.querySelectorAll('option');
|
||||
// if (newOptions.length && newOptions.length === oldOptions.length) {
|
||||
// console.log('[NG HMR] Restoring selected options.');
|
||||
// for (let index = 0; index < newOptions.length; index++) {
|
||||
// const newElement = newOptions[index];
|
||||
// newElement.selected = oldOptions[index].selected;
|
||||
|
||||
// dispatchEvents(newElement);
|
||||
// }
|
||||
// } else if (oldOptions.length) {
|
||||
// console.warn('[NG HMR] Cannot restore selected options.');
|
||||
// }
|
||||
// }
|
||||
30
packages/webpack5/src/loaders/angular-hot-loader/index.ts
Normal file
30
packages/webpack5/src/loaders/angular-hot-loader/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import { join } from 'path';
|
||||
|
||||
export const HmrLoader = __filename;
|
||||
const hmrAcceptPath = join(__dirname, './hmr-accept.js').replace(/\\/g, '/');
|
||||
|
||||
export default function (
|
||||
this: any,
|
||||
content: string,
|
||||
// Source map types are broken in the webpack type definitions
|
||||
map: any
|
||||
): void {
|
||||
const source = `${content}
|
||||
|
||||
// HMR Accept Code
|
||||
import ngHmrAccept from '${hmrAcceptPath}';
|
||||
ngHmrAccept(module);
|
||||
`;
|
||||
|
||||
this.callback(null, source, map);
|
||||
|
||||
return;
|
||||
}
|
||||
28
packages/webpack5/src/loaders/app-css-loader/index.ts
Normal file
28
packages/webpack5/src/loaders/app-css-loader/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { dedent } from 'ts-dedent';
|
||||
import { basename } from 'path';
|
||||
/**
|
||||
* This loader tries to load an `app.scss` or and `app.css` relative to the main entry
|
||||
*/
|
||||
export default function loader(content: string, map: any) {
|
||||
const { platform } = this.getOptions();
|
||||
const callback = this.async();
|
||||
const resolve = this.getResolve({
|
||||
extensions: [`.${platform}.scss`, `.${platform}.css`, '.scss', '.css'],
|
||||
});
|
||||
|
||||
resolve(this.context, './app', (err, res) => {
|
||||
if (err || !res) {
|
||||
// if we ran into an error or there's no css file found, we just return
|
||||
// original content and not append any additional imports.
|
||||
return callback(null, content, map);
|
||||
}
|
||||
|
||||
const code = dedent`
|
||||
// Added by app-css-loader
|
||||
import "./${basename(res)}";
|
||||
${content}
|
||||
`;
|
||||
|
||||
callback(null, code, map);
|
||||
});
|
||||
}
|
||||
@@ -28,7 +28,7 @@ export default function loader(content, map) {
|
||||
`
|
||||
: ``;
|
||||
|
||||
if (hasLoader('apply-css-loader')) {
|
||||
if (hasLoader('css2json-loader')) {
|
||||
content = dedent`
|
||||
${content}
|
||||
const { addTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope");
|
||||
@@ -53,5 +53,5 @@ export default function loader(content, map) {
|
||||
this.emitWarning(new Error(cssLoaderWarning));
|
||||
}
|
||||
|
||||
this.callback(null, content, map);
|
||||
this.callback(null, content, null);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function loader(content: string, map: any) {
|
||||
this.callback(
|
||||
null,
|
||||
code, //`${dependencies.join('\n')}module.exports = ${str};`,
|
||||
null
|
||||
map
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// @ts-nocheck
|
||||
// This is a runtime module - included by nativescript-hot-loader
|
||||
// this file should not include external dependencies
|
||||
// ---
|
||||
|
||||
if (module.hot) {
|
||||
let hash = __webpack_require__.h();
|
||||
|
||||
const logVerbose = (title: string, ...info: any) => {
|
||||
if (__NS_ENV_VERBOSE__) {
|
||||
console.log(`[HMR][Verbose] ${title}`);
|
||||
|
||||
if (info?.length) {
|
||||
console.log(...info);
|
||||
console.log('---');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setStatus = (
|
||||
hash: string,
|
||||
status: 'success' | 'failure',
|
||||
message?: string,
|
||||
...info: any
|
||||
): boolean => {
|
||||
// format is important - CLI expects this exact format
|
||||
console.log(`[HMR][${hash}] ${status} | ${message}`);
|
||||
if (info?.length) {
|
||||
logVerbose('Additional Info', info);
|
||||
}
|
||||
|
||||
// return true if operation was successful
|
||||
return status === 'success';
|
||||
};
|
||||
|
||||
const applyOptions = {
|
||||
ignoreUnaccepted: false,
|
||||
ignoreDeclined: false,
|
||||
ignoreErrored: false,
|
||||
onDeclined(info) {
|
||||
setStatus(hash, 'failure', 'A module has been declined.', info);
|
||||
},
|
||||
onUnaccepted(info) {
|
||||
setStatus(hash, 'failure', 'A module has not been accepted.', info);
|
||||
},
|
||||
onAccepted(info) {
|
||||
// console.log('accepted', info)
|
||||
logVerbose('Module Accepted', info);
|
||||
},
|
||||
onDisposed(info) {
|
||||
// console.log('disposed', info)
|
||||
logVerbose('Module Disposed', info);
|
||||
},
|
||||
onErrored(info) {
|
||||
setStatus(hash, 'failure', 'A module has errored.', info);
|
||||
},
|
||||
};
|
||||
|
||||
const checkAndApply = async () => {
|
||||
hash = __webpack_require__.h();
|
||||
const modules = await module.hot.check().catch((error) => {
|
||||
return setStatus(
|
||||
hash,
|
||||
'failure',
|
||||
'Failed to check.',
|
||||
error.message || error.stack
|
||||
);
|
||||
});
|
||||
|
||||
if (!modules) {
|
||||
logVerbose('No modules to apply.');
|
||||
return false;
|
||||
}
|
||||
|
||||
const appliedModules = await module.hot
|
||||
.apply(applyOptions)
|
||||
.catch((error) => {
|
||||
return setStatus(
|
||||
hash,
|
||||
'failure',
|
||||
'Failed to apply.',
|
||||
error.message || error.stack
|
||||
);
|
||||
});
|
||||
|
||||
if (!appliedModules) {
|
||||
logVerbose('No modules applied.');
|
||||
return false;
|
||||
}
|
||||
|
||||
return setStatus(hash, 'success', 'Successfully applied update.');
|
||||
};
|
||||
|
||||
const requireExists = (path) => {
|
||||
try {
|
||||
__non_webpack_require__(path);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const hasUpdate = () => {
|
||||
return [
|
||||
`~/bundle.${__webpack_hash__}.hot-update.json`,
|
||||
`~/runtime.${__webpack_hash__}.hot-update.json`,
|
||||
].some((path) => requireExists(path));
|
||||
};
|
||||
|
||||
const originalOnLiveSync = global.__onLiveSync;
|
||||
global.__onLiveSync = async function () {
|
||||
logVerbose('LiveSync');
|
||||
|
||||
if (!hasUpdate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await checkAndApply();
|
||||
originalOnLiveSync();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { relative, resolve } from 'path';
|
||||
import dedent from 'ts-dedent';
|
||||
import fs from 'fs';
|
||||
|
||||
// note: this will bail even if module.hot appears in a comment
|
||||
const MODULE_HOT_RE = /module\.hot/;
|
||||
|
||||
export default function loader(content: string, map: any) {
|
||||
if (MODULE_HOT_RE.test(content)) {
|
||||
// Code already handles HMR - we don't need to do anything
|
||||
return this.callback(null, content, map);
|
||||
}
|
||||
const opts = this.getOptions();
|
||||
|
||||
// used to inject the HMR runtime into the entry file
|
||||
if (opts.injectHMRRuntime) {
|
||||
const hmrRuntimePath = resolve(__dirname, './hmr.runtime.js');
|
||||
const hmrRuntime = fs
|
||||
.readFileSync(hmrRuntimePath)
|
||||
.toString()
|
||||
.split('// ---')[1]
|
||||
.replace('//# sourceMappingURL=hmr.runtime.js.map', '');
|
||||
|
||||
return this.callback(null, `${content}\n${hmrRuntime}`, map);
|
||||
}
|
||||
|
||||
const relativePath = relative(
|
||||
opts.appPath ?? this.rootContext,
|
||||
this.resourcePath
|
||||
).replace(/\\/g, '/');
|
||||
|
||||
const hmrCode = this.hot
|
||||
? dedent`
|
||||
/* NATIVESCRIPT-HOT-LOADER */
|
||||
if(module.hot && global._isModuleLoadedForUI && global._isModuleLoadedForUI("./${relativePath}")) {
|
||||
module.hot.accept()
|
||||
}
|
||||
`
|
||||
: ``;
|
||||
|
||||
const source = `${content}\n${hmrCode}`;
|
||||
|
||||
this.callback(null, source, map);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
const WorkerDependency = require('webpack/lib/dependencies/WorkerDependency');
|
||||
const RuntimeGlobals = require('webpack/lib/RuntimeGlobals');
|
||||
|
||||
/**
|
||||
* Patch WorkerDependency to change:
|
||||
*
|
||||
* new Worker(new URL(workerPath, baseUrl))
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
* to.
|
||||
*
|
||||
* new Worker('~/' + workerPath)
|
||||
*
|
||||
* Note: we are changing source **outside** of the dependency range, and this may
|
||||
* break when the dependency range changes, for example if this PR is merged:
|
||||
* - https://github.com/webpack/webpack/pull/12750
|
||||
*/
|
||||
WorkerDependency.Template.prototype.apply = function apply(
|
||||
dependency,
|
||||
source,
|
||||
templateContext
|
||||
) {
|
||||
const { chunkGraph, moduleGraph, runtimeRequirements } = templateContext;
|
||||
const dep = /** @type {WorkerDependency} */ dependency;
|
||||
const block = /** @type {AsyncDependenciesBlock} */ moduleGraph.getParentBlock(
|
||||
dependency
|
||||
);
|
||||
const entrypoint = /** @type {Entrypoint} */ chunkGraph.getBlockChunkGroup(
|
||||
block
|
||||
);
|
||||
const chunk = entrypoint.getEntrypointChunk();
|
||||
|
||||
// runtimeRequirements.add(RuntimeGlobals.publicPath);
|
||||
// runtimeRequirements.add(RuntimeGlobals.baseURI);
|
||||
runtimeRequirements.add(RuntimeGlobals.getChunkScriptFilename);
|
||||
|
||||
/**
|
||||
* new URL(
|
||||
* ^^^^^^^^ = 8 characters, we subtract it from the dep.range[0]
|
||||
*/
|
||||
source.replace(
|
||||
dep.range[0] - 8,
|
||||
dep.range[1],
|
||||
`/* worker import */ /* patched by nativescript-worker-loader */ '~/' + ${
|
||||
RuntimeGlobals.getChunkScriptFilename
|
||||
}(${JSON.stringify(chunk.id)})`
|
||||
);
|
||||
};
|
||||
|
||||
const NEW_WORKER_WITH_STRING_RE = /new\s+Worker\((['"`].+['"`])\)/;
|
||||
|
||||
/**
|
||||
* Replaces
|
||||
* new Worker('./somePath')
|
||||
* with
|
||||
* new Worker(new URL('./somePath', import.meta.url))
|
||||
*/
|
||||
export default function loader(content: string, map: any) {
|
||||
const source = content.replace(
|
||||
NEW_WORKER_WITH_STRING_RE,
|
||||
'new Worker(new URL($1, import.meta.url))'
|
||||
);
|
||||
this.callback(null, source, map);
|
||||
}
|
||||
@@ -178,11 +178,21 @@ async function parseXML(content: string): Promise<ParseResult> {
|
||||
.replace(/\u2028/g, '\\u2028')
|
||||
.replace(/\u2029/g, '\\u2029');
|
||||
|
||||
const hmrCode = this.hot
|
||||
? dedent`
|
||||
if(module.hot) {
|
||||
module.hot.accept()
|
||||
// module.hot.dispose(() => {})
|
||||
}
|
||||
`
|
||||
: ``;
|
||||
|
||||
const code = dedent`
|
||||
${moduleRegisters.join('\n')}
|
||||
/* XML-NAMESPACE-LOADER */
|
||||
const ___XML_NAMESPACE_LOADER_EXPORT___ = ${xml}
|
||||
export default ___XML_NAMESPACE_LOADER_EXPORT___
|
||||
${hmrCode}
|
||||
`;
|
||||
|
||||
if (errors.length) {
|
||||
|
||||
@@ -3,8 +3,13 @@ import { basename } from "path";
|
||||
import { INativeScriptPlatform } from "../helpers/platform";
|
||||
import { getProjectRootPath } from "../helpers/project";
|
||||
|
||||
function sanitizeName(appName: string): string {
|
||||
return appName.split("").filter((c) =>
|
||||
/[a-zA-Z0-9]/.test(c)
|
||||
).join("");
|
||||
}
|
||||
function getDistPath() {
|
||||
const appName = basename(getProjectRootPath());
|
||||
const appName = sanitizeName(basename(getProjectRootPath()));
|
||||
return `platforms/ios/${appName}/app`;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,14 +34,11 @@ export class PlatformSuffixPlugin {
|
||||
}
|
||||
|
||||
apply(compiler: any) {
|
||||
console.log(
|
||||
// this.extensions,
|
||||
this.platform
|
||||
);
|
||||
const platformRE = new RegExp(`\.${this.platform}\.`);
|
||||
|
||||
// require.context
|
||||
compiler.hooks.contextModuleFactory.tap(id, (cmf) => {
|
||||
// @ts-ignore
|
||||
cmf.hooks.alternativeRequests.tap(id, (modules, options) => {
|
||||
const additionalModules = [];
|
||||
// we are looking for modules that are platform specific (something.<platform>.ext)
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import webpack from 'webpack';
|
||||
|
||||
const id = 'WatchStateLoggerPlugin';
|
||||
|
||||
export enum messages {
|
||||
compilationComplete = 'Webpack compilation complete.',
|
||||
startWatching = 'Webpack compilation complete. Watching for file changes.',
|
||||
changeDetected = 'File change detected. Starting incremental webpack compilation...',
|
||||
}
|
||||
|
||||
/**
|
||||
* This little plugin will report the webpack state through the console.
|
||||
* So the {N} CLI can get some idea when compilation completes.
|
||||
* @deprecated todo: remove soon
|
||||
*/
|
||||
export class WatchStateLoggerPlugin {
|
||||
isRunningWatching: boolean;
|
||||
|
||||
apply(compiler) {
|
||||
const plugin = this;
|
||||
|
||||
compiler.hooks.watchRun.tapAsync(id, function (compiler, callback) {
|
||||
plugin.isRunningWatching = true;
|
||||
|
||||
if (plugin.isRunningWatching) {
|
||||
console.log(messages.changeDetected);
|
||||
}
|
||||
|
||||
notify(messages.changeDetected);
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) {
|
||||
callback();
|
||||
|
||||
if (plugin.isRunningWatching) {
|
||||
console.log(messages.startWatching);
|
||||
} else {
|
||||
console.log(messages.compilationComplete);
|
||||
}
|
||||
|
||||
const emittedFiles = Array.from(compilation.emittedAssets);
|
||||
const chunkFiles = getChunkFiles(compilation);
|
||||
|
||||
notify(messages.compilationComplete);
|
||||
|
||||
// Send emitted files so they can be LiveSynced if need be
|
||||
notify({ emittedFiles, chunkFiles, hash: compilation.hash });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getChunkFiles(compilation: webpack.Compilation) {
|
||||
const chunkFiles = [];
|
||||
try {
|
||||
compilation.chunks.forEach((chunk) => {
|
||||
chunk.files.forEach((file) => {
|
||||
if (file.indexOf('hot-update') === -1) {
|
||||
chunkFiles.push(file);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Warning: Unable to find chunk files.');
|
||||
}
|
||||
|
||||
return chunkFiles;
|
||||
}
|
||||
|
||||
function notify(message: any) {
|
||||
if (!process.send) {
|
||||
return;
|
||||
}
|
||||
|
||||
process.send(message, (error) => {
|
||||
if (error) {
|
||||
console.error(`[${id}] Process Send Error: `, error);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { env } from '../';
|
||||
|
||||
const id = 'WatchStatePlugin';
|
||||
const version = 1;
|
||||
|
||||
@@ -8,12 +10,10 @@ export enum messages {
|
||||
}
|
||||
|
||||
/**
|
||||
* This little plugin will report the webpack state through the console.
|
||||
* So the {N} CLI can get some idea when compilation completes.
|
||||
* This little plugin will report the webpack state through the console
|
||||
* and send status updates through IPC to the {N} CLI.
|
||||
*/
|
||||
export class WatchStatePlugin {
|
||||
isRunningWatching: boolean;
|
||||
|
||||
apply(compiler: any) {
|
||||
let isWatchMode = false;
|
||||
let prevAssets = [];
|
||||
@@ -21,8 +21,24 @@ export class WatchStatePlugin {
|
||||
compiler.hooks.watchRun.tapAsync(id, function (compiler, callback) {
|
||||
callback();
|
||||
|
||||
if (isWatchMode) {
|
||||
console.log(messages.changeDetected);
|
||||
|
||||
if (env.verbose) {
|
||||
if (compiler.modifiedFiles) {
|
||||
Array.from(compiler.modifiedFiles).forEach((file) => {
|
||||
console.log(`[${id}][WatchTriggers] MODIFIED: ${file}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (compiler.removedFiles) {
|
||||
Array.from(compiler.removedFiles).forEach((file) => {
|
||||
console.log(`[${id}][WatchTriggers] REMOVED: ${file}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
isWatchMode = true;
|
||||
console.log(messages.changeDetected);
|
||||
});
|
||||
|
||||
compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) {
|
||||
@@ -53,21 +69,23 @@ export class WatchStatePlugin {
|
||||
notify({
|
||||
type: 'compilation',
|
||||
version,
|
||||
|
||||
emittedAssets,
|
||||
staleAssets,
|
||||
hash: compilation.hash,
|
||||
|
||||
data: {
|
||||
emittedAssets,
|
||||
staleAssets,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function notify(message: any) {
|
||||
env.verbose && console.log(`[${id}] Notify: `, message);
|
||||
if (!process.send) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[${id}] Notify: `, message);
|
||||
process.send(message, (error) => {
|
||||
if (error) {
|
||||
console.error(`[${id}] Process Send Error: `, error);
|
||||
|
||||
@@ -3,7 +3,8 @@ const webpack = require("@nativescript/webpack");
|
||||
module.exports = (env) => {
|
||||
webpack.init(env);
|
||||
|
||||
// todo: comments for common usage
|
||||
// Learn how to customize:
|
||||
// https://docs.nativescript.org/webpack
|
||||
|
||||
return webpack.resolveConfig();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user