feat: external config loading

+refactor many pieces
This commit is contained in:
Igor Randjelovic
2020-11-21 13:34:09 +01:00
committed by Nathan Walker
parent 04d989c2e6
commit 575130c712
15 changed files with 358 additions and 107 deletions

View File

@ -97,9 +97,9 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR
{ {
loader: 'apply-css-loader' loader: 'apply-css-loader'
}, },
/* config.module.rule('css').use('css-loader') */ /* config.module.rule('css').use('css2json-loader') */
{ {
loader: 'css-loader' loader: 'css2json-loader'
} }
] ]
}, },
@ -107,6 +107,10 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR
{ {
test: /\\\\.scss$/, test: /\\\\.scss$/,
use: [ use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */ /* config.module.rule('scss').use('css2json-loader') */
{ {
loader: 'css2json-loader' loader: 'css2json-loader'
@ -120,6 +124,16 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR
] ]
}, },
optimization: { optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [ minimizer: [
/* config.optimization.minimizer('TerserPlugin') */ /* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin( new TerserPlugin(
@ -142,22 +156,27 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR
cleanOnceBeforeBuildPatterns: [ cleanOnceBeforeBuildPatterns: [
'__jest__/platforms/android/app/src/main/assets/app/**/*' '__jest__/platforms/android/app/src/main/assets/app/**/*'
], ],
verbose: true verbose: false
} }
), ),
/* config.plugin('DefinePlugin') */ /* config.plugin('DefinePlugin') */
new DefinePlugin( new DefinePlugin(
{ {
'global.NS_WEBPACK': true, __DEV__: true,
__NS_WEBPACK__: true,
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: true,
__IOS__: false,
'global.isAndroid': true, 'global.isAndroid': true,
'global.isIOS': false, 'global.isIOS': false,
process: 'global.process', process: 'global.process',
profile: '() => {}', profile: '() => {}',
__DEV__: 'true',
__TEST__: 'false', __TEST__: 'false',
'process.env.NODE_ENV': '\\"development\\"' 'process.env.NODE_ENV': '\\"development\\"'
} }
), ),
/* config.plugin('BundleAnalyzerPlugin') */
new BundleAnalyzerPlugin(),
/* config.plugin('WatchStateLoggerPlugin') */ /* config.plugin('WatchStateLoggerPlugin') */
new WatchStateLoggerPlugin(), new WatchStateLoggerPlugin(),
/* config.plugin('ReactRefreshWebpackPlugin') */ /* config.plugin('ReactRefreshWebpackPlugin') */
@ -273,9 +292,9 @@ exports[`react configuration > android > base config 1`] = `
{ {
loader: 'apply-css-loader' loader: 'apply-css-loader'
}, },
/* config.module.rule('css').use('css-loader') */ /* config.module.rule('css').use('css2json-loader') */
{ {
loader: 'css-loader' loader: 'css2json-loader'
} }
] ]
}, },
@ -283,6 +302,10 @@ exports[`react configuration > android > base config 1`] = `
{ {
test: /\\\\.scss$/, test: /\\\\.scss$/,
use: [ use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */ /* config.module.rule('scss').use('css2json-loader') */
{ {
loader: 'css2json-loader' loader: 'css2json-loader'
@ -296,6 +319,16 @@ exports[`react configuration > android > base config 1`] = `
] ]
}, },
optimization: { optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [ minimizer: [
/* config.optimization.minimizer('TerserPlugin') */ /* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin( new TerserPlugin(
@ -318,22 +351,27 @@ exports[`react configuration > android > base config 1`] = `
cleanOnceBeforeBuildPatterns: [ cleanOnceBeforeBuildPatterns: [
'__jest__/platforms/android/app/src/main/assets/app/**/*' '__jest__/platforms/android/app/src/main/assets/app/**/*'
], ],
verbose: true verbose: false
} }
), ),
/* config.plugin('DefinePlugin') */ /* config.plugin('DefinePlugin') */
new DefinePlugin( new DefinePlugin(
{ {
'global.NS_WEBPACK': true, __DEV__: true,
__NS_WEBPACK__: true,
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: true,
__IOS__: false,
'global.isAndroid': true, 'global.isAndroid': true,
'global.isIOS': false, 'global.isIOS': false,
process: 'global.process', process: 'global.process',
profile: '() => {}', profile: '() => {}',
__DEV__: 'true',
__TEST__: 'false', __TEST__: 'false',
'process.env.NODE_ENV': '\\"development\\"' 'process.env.NODE_ENV': '\\"development\\"'
} }
), ),
/* config.plugin('BundleAnalyzerPlugin') */
new BundleAnalyzerPlugin(),
/* config.plugin('WatchStateLoggerPlugin') */ /* config.plugin('WatchStateLoggerPlugin') */
new WatchStateLoggerPlugin() new WatchStateLoggerPlugin()
], ],
@ -442,9 +480,9 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena
{ {
loader: 'apply-css-loader' loader: 'apply-css-loader'
}, },
/* config.module.rule('css').use('css-loader') */ /* config.module.rule('css').use('css2json-loader') */
{ {
loader: 'css-loader' loader: 'css2json-loader'
} }
] ]
}, },
@ -452,6 +490,10 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena
{ {
test: /\\\\.scss$/, test: /\\\\.scss$/,
use: [ use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */ /* config.module.rule('scss').use('css2json-loader') */
{ {
loader: 'css2json-loader' loader: 'css2json-loader'
@ -465,6 +507,16 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena
] ]
}, },
optimization: { optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [ minimizer: [
/* config.optimization.minimizer('TerserPlugin') */ /* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin( new TerserPlugin(
@ -487,22 +539,27 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena
cleanOnceBeforeBuildPatterns: [ cleanOnceBeforeBuildPatterns: [
'__jest__/platforms/ios/__jest__/app/**/*' '__jest__/platforms/ios/__jest__/app/**/*'
], ],
verbose: true verbose: false
} }
), ),
/* config.plugin('DefinePlugin') */ /* config.plugin('DefinePlugin') */
new DefinePlugin( new DefinePlugin(
{ {
'global.NS_WEBPACK': true, __DEV__: true,
__NS_WEBPACK__: true,
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: false,
__IOS__: true,
'global.isAndroid': false, 'global.isAndroid': false,
'global.isIOS': true, 'global.isIOS': true,
process: 'global.process', process: 'global.process',
profile: '() => {}', profile: '() => {}',
__DEV__: 'true',
__TEST__: 'false', __TEST__: 'false',
'process.env.NODE_ENV': '\\"development\\"' 'process.env.NODE_ENV': '\\"development\\"'
} }
), ),
/* config.plugin('BundleAnalyzerPlugin') */
new BundleAnalyzerPlugin(),
/* config.plugin('WatchStateLoggerPlugin') */ /* config.plugin('WatchStateLoggerPlugin') */
new WatchStateLoggerPlugin(), new WatchStateLoggerPlugin(),
/* config.plugin('ReactRefreshWebpackPlugin') */ /* config.plugin('ReactRefreshWebpackPlugin') */
@ -621,9 +678,9 @@ exports[`react configuration > ios > base config 1`] = `
{ {
loader: 'apply-css-loader' loader: 'apply-css-loader'
}, },
/* config.module.rule('css').use('css-loader') */ /* config.module.rule('css').use('css2json-loader') */
{ {
loader: 'css-loader' loader: 'css2json-loader'
} }
] ]
}, },
@ -631,6 +688,10 @@ exports[`react configuration > ios > base config 1`] = `
{ {
test: /\\\\.scss$/, test: /\\\\.scss$/,
use: [ use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */ /* config.module.rule('scss').use('css2json-loader') */
{ {
loader: 'css2json-loader' loader: 'css2json-loader'
@ -644,6 +705,16 @@ exports[`react configuration > ios > base config 1`] = `
] ]
}, },
optimization: { optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [ minimizer: [
/* config.optimization.minimizer('TerserPlugin') */ /* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin( new TerserPlugin(
@ -666,22 +737,27 @@ exports[`react configuration > ios > base config 1`] = `
cleanOnceBeforeBuildPatterns: [ cleanOnceBeforeBuildPatterns: [
'__jest__/platforms/ios/__jest__/app/**/*' '__jest__/platforms/ios/__jest__/app/**/*'
], ],
verbose: true verbose: false
} }
), ),
/* config.plugin('DefinePlugin') */ /* config.plugin('DefinePlugin') */
new DefinePlugin( new DefinePlugin(
{ {
'global.NS_WEBPACK': true, __DEV__: true,
__NS_WEBPACK__: true,
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: false,
__IOS__: true,
'global.isAndroid': false, 'global.isAndroid': false,
'global.isIOS': true, 'global.isIOS': true,
process: 'global.process', process: 'global.process',
profile: '() => {}', profile: '() => {}',
__DEV__: 'true',
__TEST__: 'false', __TEST__: 'false',
'process.env.NODE_ENV': '\\"development\\"' 'process.env.NODE_ENV': '\\"development\\"'
} }
), ),
/* config.plugin('BundleAnalyzerPlugin') */
new BundleAnalyzerPlugin(),
/* config.plugin('WatchStateLoggerPlugin') */ /* config.plugin('WatchStateLoggerPlugin') */
new WatchStateLoggerPlugin() new WatchStateLoggerPlugin()
], ],

View File

@ -88,9 +88,9 @@ exports[`vue configuration for android 1`] = `
{ {
loader: 'apply-css-loader' loader: 'apply-css-loader'
}, },
/* config.module.rule('css').use('css-loader') */ /* config.module.rule('css').use('css2json-loader') */
{ {
loader: 'css-loader' loader: 'css2json-loader'
} }
] ]
}, },
@ -98,6 +98,10 @@ exports[`vue configuration for android 1`] = `
{ {
test: /\\\\.scss$/, test: /\\\\.scss$/,
use: [ use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */ /* config.module.rule('scss').use('css2json-loader') */
{ {
loader: 'css2json-loader' loader: 'css2json-loader'
@ -124,6 +128,16 @@ exports[`vue configuration for android 1`] = `
] ]
}, },
optimization: { optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [ minimizer: [
/* config.optimization.minimizer('TerserPlugin') */ /* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin( new TerserPlugin(
@ -148,19 +162,25 @@ exports[`vue configuration for android 1`] = `
cleanOnceBeforeBuildPatterns: [ cleanOnceBeforeBuildPatterns: [
'__jest__/platforms/android/app/src/main/assets/app/**/*' '__jest__/platforms/android/app/src/main/assets/app/**/*'
], ],
verbose: true verbose: false
} }
), ),
/* config.plugin('DefinePlugin') */ /* config.plugin('DefinePlugin') */
new DefinePlugin( new DefinePlugin(
{ {
'global.NS_WEBPACK': true, __DEV__: true,
__NS_WEBPACK__: true,
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: true,
__IOS__: false,
'global.isAndroid': true, 'global.isAndroid': true,
'global.isIOS': false, 'global.isIOS': false,
process: 'global.process', process: 'global.process',
profile: '() => {}' profile: '() => {}'
} }
), ),
/* config.plugin('BundleAnalyzerPlugin') */
new BundleAnalyzerPlugin(),
/* config.plugin('WatchStateLoggerPlugin') */ /* config.plugin('WatchStateLoggerPlugin') */
new WatchStateLoggerPlugin() new WatchStateLoggerPlugin()
], ],
@ -260,9 +280,9 @@ exports[`vue configuration for ios 1`] = `
{ {
loader: 'apply-css-loader' loader: 'apply-css-loader'
}, },
/* config.module.rule('css').use('css-loader') */ /* config.module.rule('css').use('css2json-loader') */
{ {
loader: 'css-loader' loader: 'css2json-loader'
} }
] ]
}, },
@ -270,6 +290,10 @@ exports[`vue configuration for ios 1`] = `
{ {
test: /\\\\.scss$/, test: /\\\\.scss$/,
use: [ use: [
/* config.module.rule('scss').use('apply-css-loader') */
{
loader: 'apply-css-loader'
},
/* config.module.rule('scss').use('css2json-loader') */ /* config.module.rule('scss').use('css2json-loader') */
{ {
loader: 'css2json-loader' loader: 'css2json-loader'
@ -296,6 +320,16 @@ exports[`vue configuration for ios 1`] = `
] ]
}, },
optimization: { optimization: {
splitChunks: {
cacheGroups: {
defaultVendor: {
test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/,
priority: -10,
name: 'vendor',
chunks: 'all'
}
}
},
minimizer: [ minimizer: [
/* config.optimization.minimizer('TerserPlugin') */ /* config.optimization.minimizer('TerserPlugin') */
new TerserPlugin( new TerserPlugin(
@ -320,19 +354,25 @@ exports[`vue configuration for ios 1`] = `
cleanOnceBeforeBuildPatterns: [ cleanOnceBeforeBuildPatterns: [
'__jest__/platforms/ios/__jest__/app/**/*' '__jest__/platforms/ios/__jest__/app/**/*'
], ],
verbose: true verbose: false
} }
), ),
/* config.plugin('DefinePlugin') */ /* config.plugin('DefinePlugin') */
new DefinePlugin( new DefinePlugin(
{ {
'global.NS_WEBPACK': true, __DEV__: true,
__NS_WEBPACK__: true,
__CSS_PARSER__: '\\"css-tree\\"',
__ANDROID__: false,
__IOS__: true,
'global.isAndroid': false, 'global.isAndroid': false,
'global.isIOS': true, 'global.isIOS': true,
process: 'global.process', process: 'global.process',
profile: '() => {}' profile: '() => {}'
} }
), ),
/* config.plugin('BundleAnalyzerPlugin') */
new BundleAnalyzerPlugin(),
/* config.plugin('WatchStateLoggerPlugin') */ /* config.plugin('WatchStateLoggerPlugin') */
new WatchStateLoggerPlugin() new WatchStateLoggerPlugin()
], ],

View File

@ -1,10 +1,8 @@
import Config from 'webpack-chain'; import Config from 'webpack-chain';
import { IWebpackEnv, Platform } from '../index'; import { IWebpackEnv } from '../index';
import { import {
getAbsoluteDistPath, getAbsoluteDistPath,
getDistPath,
getEntryPath, getEntryPath,
getPackageJson,
getPlatform, getPlatform,
} from '../helpers/project'; } from '../helpers/project';
@ -17,19 +15,21 @@ import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
export default function (config: Config, env: IWebpackEnv): Config { export default function (config: Config, env: IWebpackEnv): Config {
const entryPath = getEntryPath(); const entryPath = getEntryPath();
const platform = getPlatform(); const platform = getPlatform();
const packageJson = getPackageJson();
const mode = env.production ? 'production' : 'development'; const mode = env.production ? 'production' : 'development';
// set mode // set mode
config.mode(mode); config.mode(mode);
// package.json is generated by the CLI with runtime options
// this ensures it's not included in the bundle
config.externals(['package.json']); config.externals(['package.json']);
// todo: devtool // todo: devtool
config.devtool('inline-source-map'); config.devtool('inline-source-map');
// todo: figure out easiest way to make "node" target work in ns, // todo: figure out easiest way to make "node" target work in ns
// rather than the custom ns target implementation that's hard to maintain // rather than the custom ns target implementation that's hard to maintain
// appears to be working - but we still have to deal with HMR
config.target('node'); config.target('node');
config.entry('bundle').add(entryPath); config.entry('bundle').add(entryPath);
@ -62,11 +62,6 @@ export default function (config: Config, env: IWebpackEnv): Config {
priority: -10, priority: -10,
name: 'vendor', name: 'vendor',
chunks: 'all', chunks: 'all',
// test: (module) => {
// const moduleName = module.nameForCondition ? module.nameForCondition() : '';
// return /[\\/]node_modules[\\/]/.test(moduleName);
// },
// enforce: true
}, },
}, },
}); });
@ -74,6 +69,7 @@ export default function (config: Config, env: IWebpackEnv): Config {
// look for loaders in // look for loaders in
// - node_modules/@nativescript/webpack/dist/loaders // - node_modules/@nativescript/webpack/dist/loaders
// - node_modules // - node_modules
// allows for cleaner rules, without having to specify full paths to loaders
config.resolveLoader.modules config.resolveLoader.modules
.add('node_modules/@nativescript/webpack/dist/loaders') .add('node_modules/@nativescript/webpack/dist/loaders')
.add('node_modules'); .add('node_modules');
@ -149,6 +145,9 @@ export default function (config: Config, env: IWebpackEnv): Config {
config.module config.module
.rule('scss') .rule('scss')
.test(/\.scss$/) .test(/\.scss$/)
.use('apply-css-loader')
.loader('apply-css-loader')
.end()
.use('css2json-loader') .use('css2json-loader')
.loader('css2json-loader') .loader('css2json-loader')
.end() .end()
@ -159,18 +158,22 @@ export default function (config: Config, env: IWebpackEnv): Config {
config.plugin('CleanWebpackPlugin').use(CleanWebpackPlugin, [ config.plugin('CleanWebpackPlugin').use(CleanWebpackPlugin, [
{ {
cleanOnceBeforeBuildPatterns: [`${getAbsoluteDistPath()}/**/*`], cleanOnceBeforeBuildPatterns: [`${getAbsoluteDistPath()}/**/*`],
verbose: true, verbose: !!env.verbose,
}, },
]); ]);
// todo: refine defaults // todo: refine defaults
config.plugin('DefinePlugin').use(DefinePlugin, [ config.plugin('DefinePlugin').use(DefinePlugin, [
{ {
'global.NS_WEBPACK': true, __DEV__: mode === 'development',
'global.isAndroid': platform === 'android', __NS_WEBPACK__: true,
'global.isIOS': platform === 'ios', __CSS_PARSER__: JSON.stringify('css-tree'), // todo: replace from config value
__ANDROID__: platform === 'android',
__IOS__: platform === 'ios',
/* for compat only */ 'global.isAndroid': platform === 'android',
/* for compat only */ 'global.isIOS': platform === 'ios',
process: 'global.process', process: 'global.process',
profile: '() => {}', /* todo: remove if fixed in core? */ profile: '() => {}',
}, },
]); ]);
@ -183,6 +186,7 @@ export default function (config: Config, env: IWebpackEnv): Config {
// }, // },
// ]); // ]);
// todo: make opt-in with a flag
config.plugin('BundleAnalyzerPlugin').use(BundleAnalyzerPlugin); config.plugin('BundleAnalyzerPlugin').use(BundleAnalyzerPlugin);
// add the WatchStateLogger plugin used to notify the CLI of build state // add the WatchStateLogger plugin used to notify the CLI of build state

View File

@ -8,9 +8,11 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
base(config, env); base(config, env);
const platform = getPlatform(); const platform = getPlatform();
const mode = env.production ? 'production' : 'development';
const production = mode === 'production';
// todo: use env // todo: use env
let isAnySourceMapEnabled = true; let isAnySourceMapEnabled = true;
let production = false;
config.resolve.extensions.prepend('.tsx').prepend(`.${platform}.tsx`); config.resolve.extensions.prepend('.tsx').prepend(`.${platform}.tsx`);
config.resolve.alias.set('react-dom', 'react-nativescript'); config.resolve.alias.set('react-dom', 'react-nativescript');
@ -30,15 +32,12 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
config.plugin('DefinePlugin').tap((args) => { config.plugin('DefinePlugin').tap((args) => {
args[0] = merge(args[0], { args[0] = merge(args[0], {
/** For various libraries in the React ecosystem. */ /** For various libraries in the React ecosystem. */
__DEV__: production ? 'false' : 'true',
__TEST__: 'false', __TEST__: 'false',
/** /**
* Primarily for React Fast Refresh plugin, but technically the allowHmrInProduction option could be used instead. * Primarily for React Fast Refresh plugin, but technically the allowHmrInProduction option could be used instead.
* Worth including anyway, as there are plenty of Node libraries that use this flag. * Worth including anyway, as there are plenty of Node libraries that use this flag.
*/ */
'process.env.NODE_ENV': JSON.stringify( 'process.env.NODE_ENV': JSON.stringify(mode),
production ? 'production' : 'development'
),
}); });
return args; return args;

View File

@ -23,6 +23,8 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
.tap((options) => { .tap((options) => {
return { return {
...options, ...options,
// todo: should be a compiler object
// but we want it as an external dependency
compiler: 'nativescript-vue-template-compiler', compiler: 'nativescript-vue-template-compiler',
}; };
}) })

View File

@ -0,0 +1,24 @@
import { getPackageJson, getProjectRootPath } from './project';
import path from 'path';
export function getAllDependencies(): string[] {
const packageJSON = getPackageJson();
console.log(packageJSON);
return [
...Object.keys(packageJSON.dependencies ?? {}),
...Object.keys(packageJSON.devDependencies ?? {}),
];
}
export function getDependencyPath(dependencyName: string): string | null {
try {
const resolvedPath = require.resolve(`${dependencyName}/package.json`, {
paths: [getProjectRootPath()],
});
return path.dirname(resolvedPath);
} catch (err) {
return null;
}
}

View File

@ -1,11 +0,0 @@
// todo: refine
export function error(message: string, info?: { possibleCauses?: string[] }) {
console.error(`
NativeScript Webpack encountered an error and cannot proceed with the build:
${message}
Possible causes:
${info?.possibleCauses?.map((cause) => `- ${cause}`).join('\n')}
`);
}

View File

@ -0,0 +1,36 @@
import path from 'path';
import fs from 'fs';
import dedent from 'ts-dedent';
import * as lib from '../index';
import { error, info } from './log';
import { getAllDependencies, getDependencyPath } from './dependencies';
export function applyExternalConfigs() {
getAllDependencies().forEach((dependency) => {
const packagePath = getDependencyPath(dependency);
const configPath = path.join(packagePath, 'nativescript.webpack.js');
if (fs.existsSync(configPath)) {
info(`Discovered config: ${configPath}`);
try {
const externalConfig = require(configPath);
if (typeof externalConfig === 'function') {
externalConfig(lib);
} else {
// todo: warn user
// todo: perhaps support exported objects to merge into config?
}
} catch (err) {
error(
dedent`
Unable to apply config: ${configPath}.
Error is:
`,
err
);
}
}
});
}

View File

@ -1,18 +1,44 @@
import { defaultConfigs } from '@nativescript/webpack'; import { defaultConfigs } from '@nativescript/webpack';
import { getAllDependencies } from './dependencies';
import { error } from './log';
import dedent from 'ts-dedent';
export function determineProjectFlavor(): keyof typeof defaultConfigs { export function determineProjectFlavor(): keyof typeof defaultConfigs | false {
// todo; const dependencies = getAllDependencies();
// error(`
// Could not determine project flavor.
//
// Please use webpack.useConfig('<flavor>') to explicitly set the base config.
// `, {
// possibleCauses: [
// 'Not in a NativeScript project',
// 'The project is not at the current working directory'
// ]
// })
if (dependencies.includes('nativescript-vue')) {
return 'vue'; return 'vue';
} }
if (dependencies.includes('@nativescript/angular')) {
return 'angular';
}
if (dependencies.includes('react-nativescript')) {
return 'react';
}
if (dependencies.includes('svelte-native')) {
return 'svelte';
}
// the order is important - angular, react, and svelte also include these deps
// but should return prior to this condition!
if (
dependencies.includes('@nativescript/core') &&
dependencies.includes('typescript')
) {
return 'typescript';
}
if (dependencies.includes('@nativescript/core')) {
return 'javascript';
}
error(dedent`
Could not determine project flavor.
Please use webpack.useConfig('<flavor>') to explicitly set the base config.
`);
return false;
}

View File

@ -0,0 +1,23 @@
// todo: refine
// export function error(message: string, info?: { possibleCauses?: string[] }) {
// console.error(`
// NativeScript Webpack encountered an error and cannot proceed with the build:
//
// ${message}
//
// Possible causes:
// ${info?.possibleCauses?.map((cause) => `- ${cause}`).join('\n')}
// `);
// }
export function error(...data: any) {
console.error(`[@nativescript/webpack]`, ...data);
}
export function warn(...data: any) {
console.warn(`[@nativescript/webpack]`, ...data);
}
export function info(...data: any) {
console.info(`[@nativescript/webpack]`, ...data);
}

View File

@ -1,5 +1,6 @@
import { env, Platform } from '../index'; import { env, Platform } from '../index';
import { resolve, basename } from 'path'; import { resolve, basename } from 'path';
import { error } from './log';
export function getProjectRootPath(): string { export function getProjectRootPath(): string {
// todo: find actual path? // todo: find actual path?
@ -42,8 +43,7 @@ export function getPlatform(): Platform {
return 'ios'; return 'ios';
} }
// todo: maybe no throw? error('You need to provide a target platform!');
throw new Error('You need to provide a target platform!');
} }
interface IPackageJson { interface IPackageJson {

View File

@ -1,8 +1,9 @@
import Config from 'webpack-chain'; import Config from 'webpack-chain';
import webpack from 'webpack'; import webpack from 'webpack';
import { highlight } from 'cli-highlight';
import { configs } from './configuration'; import { configs } from './configuration';
import { determineProjectFlavor } from './helpers/flavor'; import { determineProjectFlavor } from './helpers/flavor';
import { highlight } from 'cli-highlight'; import { applyExternalConfigs } from './helpers/externalConfigs';
export type Platform = 'android' | 'ios' | string; export type Platform = 'android' | 'ios' | string;
@ -49,11 +50,18 @@ export function useConfig(config: keyof typeof defaultConfigs | false) {
} }
} }
export function chainWebpack(chainFn: (config: Config, env: IWebpackEnv) => any) { export function chainWebpack(
chainFn: (config: Config, env: IWebpackEnv) => any
) {
webpackChains.push(chainFn); webpackChains.push(chainFn);
} }
export function mergeWebpack(mergeFn: (config: Partial<webpack.Configuration>, env: IWebpackEnv) => any | Partial<webpack.Configuration>) { export function mergeWebpack(
mergeFn: (
config: Partial<webpack.Configuration>,
env: IWebpackEnv
) => any | Partial<webpack.Configuration>
) {
webpackMerges.push(mergeFn); webpackMerges.push(mergeFn);
} }
@ -64,6 +72,10 @@ export function resolveChainableConfig() {
useConfig(determineProjectFlavor()); useConfig(determineProjectFlavor());
} }
// apply configs from dependencies
// todo: allow opt-out
applyExternalConfigs();
// this applies all chain configs // this applies all chain configs
webpackChains.forEach((chainFn) => { webpackChains.forEach((chainFn) => {
return chainFn(config, env); return chainFn(config, env);

View File

@ -11,13 +11,11 @@ export default function loader(content, map) {
?.slice(this.loaderIndex) ?.slice(this.loaderIndex)
.some(({ path }) => path.includes(loader)); .some(({ path }) => path.includes(loader));
}; };
if (hasLoader('apply-css-loader')) {
// add a tag to the applied css // add a tag to the applied css
const tag = const tag =
this.mode === 'development' this.mode === 'development' ? `, ${JSON.stringify(this.resourcePath)}` : '';
? `, ${JSON.stringify(this.resourcePath)}`
: ''; if (hasLoader('apply-css-loader')) {
content = dedent` content = dedent`
${content} ${content}
const { addTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope"); const { addTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope");
@ -26,14 +24,12 @@ export default function loader(content, map) {
} else if (hasLoader('css-loader')) { } else if (hasLoader('css-loader')) {
content = dedent` content = dedent`
${content} ${content}
// apply css const { addTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope");
const { Application } = require("@nativescript/core");
require("@nativescript/core/ui/styling/style-scope");
if (___CSS_LOADER_EXPORT___ && typeof ___CSS_LOADER_EXPORT___.forEach === "function") { if (___CSS_LOADER_EXPORT___ && typeof ___CSS_LOADER_EXPORT___.forEach === "function") {
___CSS_LOADER_EXPORT___.forEach(cssExport => { ___CSS_LOADER_EXPORT___.forEach(cssExport => {
if (cssExport.length > 1 && cssExport[1]) { if (cssExport.length > 1 && cssExport[1]) {
// applying the second item of the export as it contains the css contents // applying the second item of the export as it contains the css contents
Application.addCss(cssExport[1]); addTaggedAdditionalCSS(cssExport[1]${tag});
} }
}); });
} }

View File

@ -13,6 +13,8 @@ export default function loader(content: string, map: any) {
const ast = parse(content); const ast = parse(content);
// todo: revise if this is necessary
// todo: perhaps use postCSS and just build imports into a single file?
let dependencies = []; let dependencies = [];
getImportRules(ast) getImportRules(ast)
.map(extractUrlFromRule) .map(extractUrlFromRule)
@ -37,9 +39,7 @@ export default function loader(content: string, map: any) {
const code = dedent` const code = dedent`
/* CSS2JSON */ /* CSS2JSON */
${dependencies.join('\n')} ${dependencies.join('\n')}
const ___CSS2JSON_LOADER_EXPORT___ = ${str} const ___CSS2JSON_LOADER_EXPORT___ = ${str}
export default ___CSS2JSON_LOADER_EXPORT___ export default ___CSS2JSON_LOADER_EXPORT___
`; `;
this.callback( this.callback(

View File

@ -1,3 +1,6 @@
import webpack from 'webpack';
const id = 'WatchStateLoggerPlugin';
export enum messages { export enum messages {
compilationComplete = 'Webpack compilation complete.', compilationComplete = 'Webpack compilation complete.',
startWatching = 'Webpack compilation complete. Watching for file changes.', startWatching = 'Webpack compilation complete. Watching for file changes.',
@ -13,15 +16,20 @@ export class WatchStateLoggerPlugin {
apply(compiler) { apply(compiler) {
const plugin = this; const plugin = this;
compiler.hooks.watchRun.tapAsync('WatchStateLoggerPlugin', function (compiler, callback) {
compiler.hooks.watchRun.tapAsync(id, function (compiler, callback) {
plugin.isRunningWatching = true; plugin.isRunningWatching = true;
if (plugin.isRunningWatching) { if (plugin.isRunningWatching) {
console.log(messages.changeDetected); console.log(messages.changeDetected);
} }
process.send && process.send(messages.changeDetected, (error) => null);
notify(messages.changeDetected);
callback(); callback();
}); });
compiler.hooks.afterEmit.tapAsync('WatchStateLoggerPlugin', function (compilation, callback) {
compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) {
callback(); callback();
if (plugin.isRunningWatching) { if (plugin.isRunningWatching) {
@ -30,18 +38,20 @@ export class WatchStateLoggerPlugin {
console.log(messages.compilationComplete); console.log(messages.compilationComplete);
} }
const emittedFiles = Object.keys(compilation.assets).filter((assetKey) => compilation.assets[assetKey].emitted); const emittedFiles = Object.keys(compilation.assets).filter(
(assetKey) => compilation.assets[assetKey].emitted
);
const chunkFiles = getChunkFiles(compilation); const chunkFiles = getChunkFiles(compilation);
process.send && process.send(messages.compilationComplete, (error) => null); notify(messages.compilationComplete);
// Send emitted files so they can be LiveSynced if need be // Send emitted files so they can be LiveSynced if need be
process.send && process.send({ emittedFiles, chunkFiles, hash: compilation.hash }, (error) => null); notify({ emittedFiles, chunkFiles, hash: compilation.hash });
}); });
} }
} }
function getChunkFiles(compilation) { function getChunkFiles(compilation: webpack.Compilation) {
const chunkFiles = []; const chunkFiles = [];
try { try {
compilation.chunks.forEach((chunk) => { compilation.chunks.forEach((chunk) => {
@ -57,3 +67,17 @@ function getChunkFiles(compilation) {
return chunkFiles; 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;
});
}