From 575130c712ec94fd676c19cdde38aa979a1374ea Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Sat, 21 Nov 2020 13:34:09 +0100 Subject: [PATCH] feat: external config loading +refactor many pieces --- .../__snapshots__/react.spec.ts.snap | 116 +++++++++++++++--- .../__snapshots__/vue.spec.ts.snap | 56 +++++++-- packages/webpack5/src/configuration/base.ts | 34 ++--- packages/webpack5/src/configuration/react.ts | 9 +- packages/webpack5/src/configuration/vue.ts | 2 + packages/webpack5/src/helpers/dependencies.ts | 24 ++++ packages/webpack5/src/helpers/errors.ts | 11 -- .../webpack5/src/helpers/externalConfigs.ts | 36 ++++++ packages/webpack5/src/helpers/flavor.ts | 52 ++++++-- packages/webpack5/src/helpers/log.ts | 23 ++++ packages/webpack5/src/helpers/project.ts | 4 +- packages/webpack5/src/index.ts | 18 ++- .../src/loaders/apply-css-loader/index.ts | 38 +++--- .../src/loaders/css2json-loader/index.ts | 4 +- .../src/plugins/WatchStateLoggerPlugin.ts | 38 ++++-- 15 files changed, 358 insertions(+), 107 deletions(-) create mode 100644 packages/webpack5/src/helpers/dependencies.ts delete mode 100644 packages/webpack5/src/helpers/errors.ts create mode 100644 packages/webpack5/src/helpers/externalConfigs.ts create mode 100644 packages/webpack5/src/helpers/log.ts diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap index 01aaba5cb..410938a57 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap @@ -97,9 +97,9 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR { 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$/, use: [ + /* config.module.rule('scss').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, /* config.module.rule('scss').use('css2json-loader') */ { loader: 'css2json-loader' @@ -120,6 +124,16 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR ] }, optimization: { + splitChunks: { + cacheGroups: { + defaultVendor: { + test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/, + priority: -10, + name: 'vendor', + chunks: 'all' + } + } + }, minimizer: [ /* config.optimization.minimizer('TerserPlugin') */ new TerserPlugin( @@ -142,22 +156,27 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR cleanOnceBeforeBuildPatterns: [ '__jest__/platforms/android/app/src/main/assets/app/**/*' ], - verbose: true + verbose: false } ), /* config.plugin('DefinePlugin') */ new DefinePlugin( { - 'global.NS_WEBPACK': true, + __DEV__: true, + __NS_WEBPACK__: true, + __CSS_PARSER__: '\\"css-tree\\"', + __ANDROID__: true, + __IOS__: false, 'global.isAndroid': true, 'global.isIOS': false, process: 'global.process', profile: '() => {}', - __DEV__: 'true', __TEST__: 'false', 'process.env.NODE_ENV': '\\"development\\"' } ), + /* config.plugin('BundleAnalyzerPlugin') */ + new BundleAnalyzerPlugin(), /* config.plugin('WatchStateLoggerPlugin') */ new WatchStateLoggerPlugin(), /* config.plugin('ReactRefreshWebpackPlugin') */ @@ -273,9 +292,9 @@ exports[`react configuration > android > base config 1`] = ` { 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$/, use: [ + /* config.module.rule('scss').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, /* config.module.rule('scss').use('css2json-loader') */ { loader: 'css2json-loader' @@ -296,6 +319,16 @@ exports[`react configuration > android > base config 1`] = ` ] }, optimization: { + splitChunks: { + cacheGroups: { + defaultVendor: { + test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/, + priority: -10, + name: 'vendor', + chunks: 'all' + } + } + }, minimizer: [ /* config.optimization.minimizer('TerserPlugin') */ new TerserPlugin( @@ -318,22 +351,27 @@ exports[`react configuration > android > base config 1`] = ` cleanOnceBeforeBuildPatterns: [ '__jest__/platforms/android/app/src/main/assets/app/**/*' ], - verbose: true + verbose: false } ), /* config.plugin('DefinePlugin') */ new DefinePlugin( { - 'global.NS_WEBPACK': true, + __DEV__: true, + __NS_WEBPACK__: true, + __CSS_PARSER__: '\\"css-tree\\"', + __ANDROID__: true, + __IOS__: false, 'global.isAndroid': true, 'global.isIOS': false, process: 'global.process', profile: '() => {}', - __DEV__: 'true', __TEST__: 'false', 'process.env.NODE_ENV': '\\"development\\"' } ), + /* config.plugin('BundleAnalyzerPlugin') */ + new BundleAnalyzerPlugin(), /* config.plugin('WatchStateLoggerPlugin') */ new WatchStateLoggerPlugin() ], @@ -442,9 +480,9 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena { 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$/, use: [ + /* config.module.rule('scss').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, /* config.module.rule('scss').use('css2json-loader') */ { loader: 'css2json-loader' @@ -465,6 +507,16 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena ] }, optimization: { + splitChunks: { + cacheGroups: { + defaultVendor: { + test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/, + priority: -10, + name: 'vendor', + chunks: 'all' + } + } + }, minimizer: [ /* config.optimization.minimizer('TerserPlugin') */ new TerserPlugin( @@ -487,22 +539,27 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena cleanOnceBeforeBuildPatterns: [ '__jest__/platforms/ios/__jest__/app/**/*' ], - verbose: true + verbose: false } ), /* config.plugin('DefinePlugin') */ new DefinePlugin( { - 'global.NS_WEBPACK': true, + __DEV__: true, + __NS_WEBPACK__: true, + __CSS_PARSER__: '\\"css-tree\\"', + __ANDROID__: false, + __IOS__: true, 'global.isAndroid': false, 'global.isIOS': true, process: 'global.process', profile: '() => {}', - __DEV__: 'true', __TEST__: 'false', 'process.env.NODE_ENV': '\\"development\\"' } ), + /* config.plugin('BundleAnalyzerPlugin') */ + new BundleAnalyzerPlugin(), /* config.plugin('WatchStateLoggerPlugin') */ new WatchStateLoggerPlugin(), /* config.plugin('ReactRefreshWebpackPlugin') */ @@ -621,9 +678,9 @@ exports[`react configuration > ios > base config 1`] = ` { 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$/, use: [ + /* config.module.rule('scss').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, /* config.module.rule('scss').use('css2json-loader') */ { loader: 'css2json-loader' @@ -644,6 +705,16 @@ exports[`react configuration > ios > base config 1`] = ` ] }, optimization: { + splitChunks: { + cacheGroups: { + defaultVendor: { + test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/, + priority: -10, + name: 'vendor', + chunks: 'all' + } + } + }, minimizer: [ /* config.optimization.minimizer('TerserPlugin') */ new TerserPlugin( @@ -666,22 +737,27 @@ exports[`react configuration > ios > base config 1`] = ` cleanOnceBeforeBuildPatterns: [ '__jest__/platforms/ios/__jest__/app/**/*' ], - verbose: true + verbose: false } ), /* config.plugin('DefinePlugin') */ new DefinePlugin( { - 'global.NS_WEBPACK': true, + __DEV__: true, + __NS_WEBPACK__: true, + __CSS_PARSER__: '\\"css-tree\\"', + __ANDROID__: false, + __IOS__: true, 'global.isAndroid': false, 'global.isIOS': true, process: 'global.process', profile: '() => {}', - __DEV__: 'true', __TEST__: 'false', 'process.env.NODE_ENV': '\\"development\\"' } ), + /* config.plugin('BundleAnalyzerPlugin') */ + new BundleAnalyzerPlugin(), /* config.plugin('WatchStateLoggerPlugin') */ new WatchStateLoggerPlugin() ], diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap index cae33ac1d..ca9d8ae60 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap @@ -88,9 +88,9 @@ exports[`vue configuration for android 1`] = ` { 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$/, use: [ + /* config.module.rule('scss').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, /* config.module.rule('scss').use('css2json-loader') */ { loader: 'css2json-loader' @@ -124,6 +128,16 @@ exports[`vue configuration for android 1`] = ` ] }, optimization: { + splitChunks: { + cacheGroups: { + defaultVendor: { + test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/, + priority: -10, + name: 'vendor', + chunks: 'all' + } + } + }, minimizer: [ /* config.optimization.minimizer('TerserPlugin') */ new TerserPlugin( @@ -148,19 +162,25 @@ exports[`vue configuration for android 1`] = ` cleanOnceBeforeBuildPatterns: [ '__jest__/platforms/android/app/src/main/assets/app/**/*' ], - verbose: true + verbose: false } ), /* config.plugin('DefinePlugin') */ new DefinePlugin( { - 'global.NS_WEBPACK': true, + __DEV__: true, + __NS_WEBPACK__: true, + __CSS_PARSER__: '\\"css-tree\\"', + __ANDROID__: true, + __IOS__: false, 'global.isAndroid': true, 'global.isIOS': false, process: 'global.process', profile: '() => {}' } ), + /* config.plugin('BundleAnalyzerPlugin') */ + new BundleAnalyzerPlugin(), /* config.plugin('WatchStateLoggerPlugin') */ new WatchStateLoggerPlugin() ], @@ -260,9 +280,9 @@ exports[`vue configuration for ios 1`] = ` { 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$/, use: [ + /* config.module.rule('scss').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, /* config.module.rule('scss').use('css2json-loader') */ { loader: 'css2json-loader' @@ -296,6 +320,16 @@ exports[`vue configuration for ios 1`] = ` ] }, optimization: { + splitChunks: { + cacheGroups: { + defaultVendor: { + test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/, + priority: -10, + name: 'vendor', + chunks: 'all' + } + } + }, minimizer: [ /* config.optimization.minimizer('TerserPlugin') */ new TerserPlugin( @@ -320,19 +354,25 @@ exports[`vue configuration for ios 1`] = ` cleanOnceBeforeBuildPatterns: [ '__jest__/platforms/ios/__jest__/app/**/*' ], - verbose: true + verbose: false } ), /* config.plugin('DefinePlugin') */ new DefinePlugin( { - 'global.NS_WEBPACK': true, + __DEV__: true, + __NS_WEBPACK__: true, + __CSS_PARSER__: '\\"css-tree\\"', + __ANDROID__: false, + __IOS__: true, 'global.isAndroid': false, 'global.isIOS': true, process: 'global.process', profile: '() => {}' } ), + /* config.plugin('BundleAnalyzerPlugin') */ + new BundleAnalyzerPlugin(), /* config.plugin('WatchStateLoggerPlugin') */ new WatchStateLoggerPlugin() ], diff --git a/packages/webpack5/src/configuration/base.ts b/packages/webpack5/src/configuration/base.ts index b7188188f..7f92da331 100644 --- a/packages/webpack5/src/configuration/base.ts +++ b/packages/webpack5/src/configuration/base.ts @@ -1,10 +1,8 @@ import Config from 'webpack-chain'; -import { IWebpackEnv, Platform } from '../index'; +import { IWebpackEnv } from '../index'; import { getAbsoluteDistPath, - getDistPath, getEntryPath, - getPackageJson, getPlatform, } from '../helpers/project'; @@ -17,19 +15,21 @@ import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; export default function (config: Config, env: IWebpackEnv): Config { const entryPath = getEntryPath(); const platform = getPlatform(); - const packageJson = getPackageJson(); const mode = env.production ? 'production' : 'development'; // set 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']); // todo: devtool 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 + // appears to be working - but we still have to deal with HMR config.target('node'); config.entry('bundle').add(entryPath); @@ -62,11 +62,6 @@ export default function (config: Config, env: IWebpackEnv): Config { priority: -10, name: 'vendor', 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 // - node_modules/@nativescript/webpack/dist/loaders // - 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('node_modules'); @@ -149,6 +145,9 @@ export default function (config: Config, env: IWebpackEnv): Config { config.module .rule('scss') .test(/\.scss$/) + .use('apply-css-loader') + .loader('apply-css-loader') + .end() .use('css2json-loader') .loader('css2json-loader') .end() @@ -159,18 +158,22 @@ export default function (config: Config, env: IWebpackEnv): Config { config.plugin('CleanWebpackPlugin').use(CleanWebpackPlugin, [ { cleanOnceBeforeBuildPatterns: [`${getAbsoluteDistPath()}/**/*`], - verbose: true, + verbose: !!env.verbose, }, ]); // todo: refine defaults config.plugin('DefinePlugin').use(DefinePlugin, [ { - 'global.NS_WEBPACK': true, - 'global.isAndroid': platform === 'android', - 'global.isIOS': platform === 'ios', + __DEV__: mode === 'development', + __NS_WEBPACK__: true, + __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', - 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); // add the WatchStateLogger plugin used to notify the CLI of build state diff --git a/packages/webpack5/src/configuration/react.ts b/packages/webpack5/src/configuration/react.ts index db2e330a2..95b74b971 100644 --- a/packages/webpack5/src/configuration/react.ts +++ b/packages/webpack5/src/configuration/react.ts @@ -8,9 +8,11 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { base(config, env); const platform = getPlatform(); + const mode = env.production ? 'production' : 'development'; + const production = mode === 'production'; + // todo: use env let isAnySourceMapEnabled = true; - let production = false; config.resolve.extensions.prepend('.tsx').prepend(`.${platform}.tsx`); 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) => { args[0] = merge(args[0], { /** For various libraries in the React ecosystem. */ - __DEV__: production ? 'false' : 'true', __TEST__: 'false', /** * 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. */ - 'process.env.NODE_ENV': JSON.stringify( - production ? 'production' : 'development' - ), + 'process.env.NODE_ENV': JSON.stringify(mode), }); return args; diff --git a/packages/webpack5/src/configuration/vue.ts b/packages/webpack5/src/configuration/vue.ts index aa0804314..a9d34e254 100644 --- a/packages/webpack5/src/configuration/vue.ts +++ b/packages/webpack5/src/configuration/vue.ts @@ -23,6 +23,8 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { .tap((options) => { return { ...options, + // todo: should be a compiler object + // but we want it as an external dependency compiler: 'nativescript-vue-template-compiler', }; }) diff --git a/packages/webpack5/src/helpers/dependencies.ts b/packages/webpack5/src/helpers/dependencies.ts new file mode 100644 index 000000000..f3d34f885 --- /dev/null +++ b/packages/webpack5/src/helpers/dependencies.ts @@ -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; + } +} diff --git a/packages/webpack5/src/helpers/errors.ts b/packages/webpack5/src/helpers/errors.ts deleted file mode 100644 index 0dbca3e38..000000000 --- a/packages/webpack5/src/helpers/errors.ts +++ /dev/null @@ -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')} - `); -} diff --git a/packages/webpack5/src/helpers/externalConfigs.ts b/packages/webpack5/src/helpers/externalConfigs.ts new file mode 100644 index 000000000..6ef55de71 --- /dev/null +++ b/packages/webpack5/src/helpers/externalConfigs.ts @@ -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 + ); + } + } + }); +} diff --git a/packages/webpack5/src/helpers/flavor.ts b/packages/webpack5/src/helpers/flavor.ts index 202dd67eb..4b9418696 100644 --- a/packages/webpack5/src/helpers/flavor.ts +++ b/packages/webpack5/src/helpers/flavor.ts @@ -1,18 +1,44 @@ import { defaultConfigs } from '@nativescript/webpack'; +import { getAllDependencies } from './dependencies'; +import { error } from './log'; +import dedent from 'ts-dedent'; -export function determineProjectFlavor(): keyof typeof defaultConfigs { - // todo; +export function determineProjectFlavor(): keyof typeof defaultConfigs | false { + const dependencies = getAllDependencies(); - // error(` - // Could not determine project flavor. - // - // Please use webpack.useConfig('') 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('') to explicitly set the base config. + `); + + return false; } diff --git a/packages/webpack5/src/helpers/log.ts b/packages/webpack5/src/helpers/log.ts new file mode 100644 index 000000000..7cca6e00c --- /dev/null +++ b/packages/webpack5/src/helpers/log.ts @@ -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); +} diff --git a/packages/webpack5/src/helpers/project.ts b/packages/webpack5/src/helpers/project.ts index 628b200b0..dd4f40bd0 100644 --- a/packages/webpack5/src/helpers/project.ts +++ b/packages/webpack5/src/helpers/project.ts @@ -1,5 +1,6 @@ import { env, Platform } from '../index'; import { resolve, basename } from 'path'; +import { error } from './log'; export function getProjectRootPath(): string { // todo: find actual path? @@ -42,8 +43,7 @@ export function getPlatform(): Platform { return 'ios'; } - // todo: maybe no throw? - throw new Error('You need to provide a target platform!'); + error('You need to provide a target platform!'); } interface IPackageJson { diff --git a/packages/webpack5/src/index.ts b/packages/webpack5/src/index.ts index 20b30ba64..7c686be40 100644 --- a/packages/webpack5/src/index.ts +++ b/packages/webpack5/src/index.ts @@ -1,8 +1,9 @@ import Config from 'webpack-chain'; import webpack from 'webpack'; +import { highlight } from 'cli-highlight'; import { configs } from './configuration'; import { determineProjectFlavor } from './helpers/flavor'; -import { highlight } from 'cli-highlight'; +import { applyExternalConfigs } from './helpers/externalConfigs'; 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); } -export function mergeWebpack(mergeFn: (config: Partial, env: IWebpackEnv) => any | Partial) { +export function mergeWebpack( + mergeFn: ( + config: Partial, + env: IWebpackEnv + ) => any | Partial +) { webpackMerges.push(mergeFn); } @@ -64,6 +72,10 @@ export function resolveChainableConfig() { useConfig(determineProjectFlavor()); } + // apply configs from dependencies + // todo: allow opt-out + applyExternalConfigs(); + // this applies all chain configs webpackChains.forEach((chainFn) => { return chainFn(config, env); diff --git a/packages/webpack5/src/loaders/apply-css-loader/index.ts b/packages/webpack5/src/loaders/apply-css-loader/index.ts index 17247d4ad..20740d2ba 100644 --- a/packages/webpack5/src/loaders/apply-css-loader/index.ts +++ b/packages/webpack5/src/loaders/apply-css-loader/index.ts @@ -11,33 +11,29 @@ export default function loader(content, map) { ?.slice(this.loaderIndex) .some(({ path }) => path.includes(loader)); }; + // add a tag to the applied css + const tag = + this.mode === 'development' ? `, ${JSON.stringify(this.resourcePath)}` : ''; if (hasLoader('apply-css-loader')) { - // add a tag to the applied css - const tag = - this.mode === 'development' - ? `, ${JSON.stringify(this.resourcePath)}` - : ''; content = dedent` - ${content} - const { addTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope"); - addTaggedAdditionalCSS(___CSS2JSON_LOADER_EXPORT___${tag}) + ${content} + const { addTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope"); + addTaggedAdditionalCSS(___CSS2JSON_LOADER_EXPORT___${tag}) `; } else if (hasLoader('css-loader')) { content = dedent` - ${content} - // apply css - const { Application } = require("@nativescript/core"); - require("@nativescript/core/ui/styling/style-scope"); - if (___CSS_LOADER_EXPORT___ && typeof ___CSS_LOADER_EXPORT___.forEach === "function") { - ___CSS_LOADER_EXPORT___.forEach(cssExport => { - if (cssExport.length > 1 && cssExport[1]) { - // applying the second item of the export as it contains the css contents - Application.addCss(cssExport[1]); - } - }); - } - `; + ${content} + const { addTaggedAdditionalCSS } = require("@nativescript/core/ui/styling/style-scope"); + if (___CSS_LOADER_EXPORT___ && typeof ___CSS_LOADER_EXPORT___.forEach === "function") { + ___CSS_LOADER_EXPORT___.forEach(cssExport => { + if (cssExport.length > 1 && cssExport[1]) { + // applying the second item of the export as it contains the css contents + addTaggedAdditionalCSS(cssExport[1]${tag}); + } + }); + } + `; } else { this.emitWarning(new Error(cssLoaderWarning)); } diff --git a/packages/webpack5/src/loaders/css2json-loader/index.ts b/packages/webpack5/src/loaders/css2json-loader/index.ts index e325b2be7..9d02ab42e 100644 --- a/packages/webpack5/src/loaders/css2json-loader/index.ts +++ b/packages/webpack5/src/loaders/css2json-loader/index.ts @@ -13,6 +13,8 @@ export default function loader(content: string, map: any) { const ast = parse(content); + // todo: revise if this is necessary + // todo: perhaps use postCSS and just build imports into a single file? let dependencies = []; getImportRules(ast) .map(extractUrlFromRule) @@ -37,9 +39,7 @@ export default function loader(content: string, map: any) { const code = dedent` /* CSS2JSON */ ${dependencies.join('\n')} - const ___CSS2JSON_LOADER_EXPORT___ = ${str} - export default ___CSS2JSON_LOADER_EXPORT___ `; this.callback( diff --git a/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts b/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts index 0071cc315..53263012b 100644 --- a/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts +++ b/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts @@ -1,3 +1,6 @@ +import webpack from 'webpack'; + +const id = 'WatchStateLoggerPlugin'; export enum messages { compilationComplete = 'Webpack compilation complete.', startWatching = 'Webpack compilation complete. Watching for file changes.', @@ -13,15 +16,20 @@ export class WatchStateLoggerPlugin { apply(compiler) { const plugin = this; - compiler.hooks.watchRun.tapAsync('WatchStateLoggerPlugin', function (compiler, callback) { + + compiler.hooks.watchRun.tapAsync(id, function (compiler, callback) { plugin.isRunningWatching = true; + if (plugin.isRunningWatching) { console.log(messages.changeDetected); } - process.send && process.send(messages.changeDetected, (error) => null); + + notify(messages.changeDetected); + callback(); }); - compiler.hooks.afterEmit.tapAsync('WatchStateLoggerPlugin', function (compilation, callback) { + + compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) { callback(); if (plugin.isRunningWatching) { @@ -30,18 +38,20 @@ export class WatchStateLoggerPlugin { 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); - process.send && process.send(messages.compilationComplete, (error) => null); + notify(messages.compilationComplete); // 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 = []; try { compilation.chunks.forEach((chunk) => { @@ -57,3 +67,17 @@ function getChunkFiles(compilation) { 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; + }); +}