diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap index 2031cef28..6afda83b5 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`vue configuration [android] works 1`] = ` +exports[`vue configuration for android 1`] = ` "{ resolve: { symlinks: true, @@ -14,8 +14,74 @@ exports[`vue configuration [android] works 1`] = ` '.vue' ] }, + resolveLoader: { + modules: [ + '@nativescript/webpack/loaders', + 'node_modules' + ] + }, module: { rules: [ + /* config.module.rule('ts') */ + { + test: /\\\\.ts$/, + use: [ + /* config.module.rule('ts').use('ts-loader') */ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + allowTsInNodeModules: true, + compilerOptions: { + sourceMap: true, + declaration: false + }, + getCustomTransformers: function () { /* omitted long function */ }, + appendTsSuffixTo: [ + '\\\\\\\\.vue$' + ] + } + } + ] + }, + /* config.module.rule('js') */ + { + test: /\\\\.js$/, + use: [ + /* config.module.rule('js').use('babel-loader') */ + { + loader: 'babel-loader' + } + ] + }, + /* config.module.rule('css') */ + { + test: /\\\\.css$/, + use: [ + /* config.module.rule('css').use('css2json-loader') */ + { + loader: 'css2json-loader' + }, + /* config.module.rule('css').use('css-loader') */ + { + loader: 'css-loader' + } + ] + }, + /* config.module.rule('scss') */ + { + test: /\\\\.scss$/, + use: [ + /* config.module.rule('scss').use('css2json-loader') */ + { + loader: 'css2json-loader' + }, + /* config.module.rule('scss').use('scss-loader') */ + { + loader: 'scss-loader' + } + ] + }, /* config.module.rule('vue') */ { test: /\\\\.vue$/, @@ -28,20 +94,6 @@ exports[`vue configuration [android] works 1`] = ` } } ] - }, - /* config.module.rule('ts') */ - { - use: [ - /* config.module.rule('ts').use('ts-loader') */ - { - loader: 'ts-loader', - options: { - appendTsSuffixTo: [ - /\\\\.vue$/ - ] - } - } - ] } ] }, @@ -55,7 +107,24 @@ exports[`vue configuration [android] works 1`] = ` verbose: true } ), - /* config.plugin('vue-plugin') */ + /* config.plugin('define') */ + new DefinePlugin( + { + 'global.NS_WEBPACK': true, + 'global.isAndroid': true, + 'global.isIOS': false, + process: 'global.process' + } + ), + /* config.plugin('copy') */ + new CopyPluginTemp( + { + patterns: [] + } + ), + /* config.plugin('watch-state-logger') */ + new WatchStateLoggerPlugin(), + /* config.plugin('vue') */ new VueLoaderPlugin() ], entry: { @@ -66,7 +135,7 @@ exports[`vue configuration [android] works 1`] = ` }" `; -exports[`vue configuration [ios] works 1`] = ` +exports[`vue configuration for ios 1`] = ` "{ resolve: { symlinks: true, @@ -80,8 +149,74 @@ exports[`vue configuration [ios] works 1`] = ` '.vue' ] }, + resolveLoader: { + modules: [ + '@nativescript/webpack/loaders', + 'node_modules' + ] + }, module: { rules: [ + /* config.module.rule('ts') */ + { + test: /\\\\.ts$/, + use: [ + /* config.module.rule('ts').use('ts-loader') */ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + allowTsInNodeModules: true, + compilerOptions: { + sourceMap: true, + declaration: false + }, + getCustomTransformers: function () { /* omitted long function */ }, + appendTsSuffixTo: [ + '\\\\\\\\.vue$' + ] + } + } + ] + }, + /* config.module.rule('js') */ + { + test: /\\\\.js$/, + use: [ + /* config.module.rule('js').use('babel-loader') */ + { + loader: 'babel-loader' + } + ] + }, + /* config.module.rule('css') */ + { + test: /\\\\.css$/, + use: [ + /* config.module.rule('css').use('css2json-loader') */ + { + loader: 'css2json-loader' + }, + /* config.module.rule('css').use('css-loader') */ + { + loader: 'css-loader' + } + ] + }, + /* config.module.rule('scss') */ + { + test: /\\\\.scss$/, + use: [ + /* config.module.rule('scss').use('css2json-loader') */ + { + loader: 'css2json-loader' + }, + /* config.module.rule('scss').use('scss-loader') */ + { + loader: 'scss-loader' + } + ] + }, /* config.module.rule('vue') */ { test: /\\\\.vue$/, @@ -94,20 +229,6 @@ exports[`vue configuration [ios] works 1`] = ` } } ] - }, - /* config.module.rule('ts') */ - { - use: [ - /* config.module.rule('ts').use('ts-loader') */ - { - loader: 'ts-loader', - options: { - appendTsSuffixTo: [ - /\\\\.vue$/ - ] - } - } - ] } ] }, @@ -121,7 +242,24 @@ exports[`vue configuration [ios] works 1`] = ` verbose: true } ), - /* config.plugin('vue-plugin') */ + /* config.plugin('define') */ + new DefinePlugin( + { + 'global.NS_WEBPACK': true, + 'global.isAndroid': false, + 'global.isIOS': true, + process: 'global.process' + } + ), + /* config.plugin('copy') */ + new CopyPluginTemp( + { + patterns: [] + } + ), + /* config.plugin('watch-state-logger') */ + new WatchStateLoggerPlugin(), + /* config.plugin('vue') */ new VueLoaderPlugin() ], entry: { diff --git a/packages/webpack5/__tests__/configuration/vue.spec.ts b/packages/webpack5/__tests__/configuration/vue.spec.ts index 4f69ecff4..c1648e229 100644 --- a/packages/webpack5/__tests__/configuration/vue.spec.ts +++ b/packages/webpack5/__tests__/configuration/vue.spec.ts @@ -1,10 +1,16 @@ import { __vue } from '@nativescript/webpack'; +// todo: maybe mock baseConfig as we test it separately? +// import Config from 'webpack-chain' +// jest.mock('../../src/configuration/base', () => () => { +// return new Config() +// }) + describe('vue configuration', () => { const platforms = ['ios', 'android']; for (let platform of platforms) { - it(`[${platform}] works`, () => { + it(`for ${platform}`, () => { expect( __vue({ [platform]: true, diff --git a/packages/webpack5/package.json b/packages/webpack5/package.json index 7a8f2cf23..35267f3b3 100644 --- a/packages/webpack5/package.json +++ b/packages/webpack5/package.json @@ -18,6 +18,7 @@ "dependencies": { "clean-webpack-plugin": "^3.0.0", "vue-loader": "^15.9.5", - "webpack-chain": "^6.5.1" + "webpack-chain": "^6.5.1", + "webpack-merge": "^5.4.0" } } diff --git a/packages/webpack5/src/configuration/base.ts b/packages/webpack5/src/configuration/base.ts index bd20179c9..72a30dc01 100644 --- a/packages/webpack5/src/configuration/base.ts +++ b/packages/webpack5/src/configuration/base.ts @@ -2,11 +2,19 @@ import Config from 'webpack-chain'; import { IWebpackEnv, WebpackPlatform } from './index'; import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import { getDistPath } from '../helpers/projectHelpers'; +import { DefinePlugin } from 'webpack'; +import { WatchStateLoggerPlugin } from '../plugins/WatchStateLoggerPlugin'; // todo: add base configuration that's shared across all flavors export default function (env: IWebpackEnv): Config { const config = new Config(); const distPath = getDistPath(env); + const platform = determinePlatformFromEnv(env); + + // look for loaders in + // - @nativescript/webpack/loaders + // - node_modules + config.resolveLoader.modules.add('@nativescript/webpack/loaders').add('node_modules'); // inspector_modules config.when(shouldIncludeInspectorModules(env), (config) => { @@ -21,6 +29,53 @@ export default function (env: IWebpackEnv): Config { // resolve symlinks config.resolve.symlinks(true); + // set up ts support + config.module + .rule('ts') + .test(/\.ts$/) + .use('ts-loader') + .loader('ts-loader') + .options({ + // configFile: '', + transpileOnly: true, + allowTsInNodeModules: true, + compilerOptions: { + sourceMap: true, + declaration: false, + }, + getCustomTransformers() { + return { + before: [ + // todo: transform NativeClass + ], + }; + }, + }); + + // set up js + // todo: do we need babel-loader? It's useful to support it + config.module.rule('js').test(/\.js$/).use('babel-loader').loader('babel-loader'); + + // set up css + config.module + .rule('css') + .test(/\.css$/) + .use('css2json-loader') + .loader('css2json-loader') + .end() + .use('css-loader') + .loader('css-loader'); + + // set up scss + config.module + .rule('scss') + .test(/\.scss$/) + .use('css2json-loader') + .loader('css2json-loader') + .end() + .use('scss-loader') + .loader('scss-loader'); + // items to clean config.plugin('clean').use(CleanWebpackPlugin, [ { @@ -29,6 +84,28 @@ export default function (env: IWebpackEnv): Config { }, ]); + // todo: refine defaults + config.plugin('define').use(DefinePlugin, [ + { + 'global.NS_WEBPACK': true, + 'global.isAndroid': platform === WebpackPlatform.android, + 'global.isIOS': platform === WebpackPlatform.ios, + process: 'global.process', + }, + ]); + + // todo: we should probably move away from CopyWebpackPlugin + // it has many issues we can solve by simply copying files **before** the build even starts + // this is just a temp inline plugin that does nothing while building out the configs. + config.plugin('copy').use(function CopyPluginTemp() {}, [ + { + patterns: [], + }, + ]); + + // add the WatchStateLogger plugin used to notify the CLI of build state + config.plugin('watch-state-logger').use(WatchStateLoggerPlugin); + return config; } diff --git a/packages/webpack5/src/configuration/javascript.ts b/packages/webpack5/src/configuration/javascript.ts index 4826e2233..98c8a27e5 100644 --- a/packages/webpack5/src/configuration/javascript.ts +++ b/packages/webpack5/src/configuration/javascript.ts @@ -2,9 +2,16 @@ import base from './base'; import { IWebpackEnv } from '@nativescript/webpack'; import Config from 'webpack-chain'; -// todo: add base configuration for core +// todo: add base configuration for core with javascript export default function (env: IWebpackEnv): Config { const config = base(env); + // set up xml + config.module + .rule('xml') + .test(/\.xml$/) + .use('xml-loader') + .loader('xml-loader'); + return config; } diff --git a/packages/webpack5/src/configuration/vue.ts b/packages/webpack5/src/configuration/vue.ts index a03ceb937..d9502ea60 100644 --- a/packages/webpack5/src/configuration/vue.ts +++ b/packages/webpack5/src/configuration/vue.ts @@ -2,7 +2,7 @@ import base from './base'; import Config from 'webpack-chain'; import { VueLoaderPlugin } from 'vue-loader'; import { IWebpackEnv } from './index'; - +import { merge } from 'webpack-merge'; // todo: add base configuration for vue export default function (env: IWebpackEnv): Config { const config = base(env); @@ -29,15 +29,14 @@ export default function (env: IWebpackEnv): Config { .rule('ts') .use('ts-loader') .loader('ts-loader') - .tap((options) => { - return { - ...options, - appendTsSuffixTo: [/\.vue$/], - }; + .tap((options = {}) => { + return merge(options, { + appendTsSuffixTo: ['\\.vue$'], + }); }); // add VueLoaderPlugin - config.plugin('vue-plugin').use(VueLoaderPlugin); + config.plugin('vue').use(VueLoaderPlugin); // add an alias for vue, since some plugins may try to import it config.resolve.alias.set('vue', 'nativescript-vue'); diff --git a/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts b/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts new file mode 100644 index 000000000..51cd2cdb2 --- /dev/null +++ b/packages/webpack5/src/plugins/WatchStateLoggerPlugin.ts @@ -0,0 +1,58 @@ +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. + */ +export class WatchStateLoggerPlugin { + isRunningWatching: boolean; + + apply(compiler) { + const plugin = this; + compiler.hooks.watchRun.tapAsync('WatchStateLoggerPlugin', function (compiler, callback) { + plugin.isRunningWatching = true; + if (plugin.isRunningWatching) { + console.log(messages.changeDetected); + } + process.send && process.send(messages.changeDetected, (error) => null); + callback(); + }); + compiler.hooks.afterEmit.tapAsync('WatchStateLoggerPlugin', function (compilation, callback) { + callback(); + + if (plugin.isRunningWatching) { + console.log(messages.startWatching); + } else { + console.log(messages.compilationComplete); + } + + const emittedFiles = Object.keys(compilation.assets).filter((assetKey) => compilation.assets[assetKey].emitted); + + const chunkFiles = getChunkFiles(compilation); + process.send && process.send(messages.compilationComplete, (error) => null); + // Send emitted files so they can be LiveSynced if need be + process.send && process.send({ emittedFiles, chunkFiles, hash: compilation.hash }, (error) => null); + }); + } +} + +function getChunkFiles(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; +}