From 8855ca43735f6e6f6ffd3598ff3ea2d18d57fbbd Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Wed, 24 Mar 2021 17:27:35 +0100 Subject: [PATCH] chore: HMRRuntime injection (wip) --- .../__snapshots__/angular.spec.ts.snap | 32 ++++++- .../__snapshots__/base.spec.ts.snap | 32 ++++++- .../__snapshots__/javascript.spec.ts.snap | 32 ++++++- .../__snapshots__/react.spec.ts.snap | 64 ++++++++++++- .../__snapshots__/svelte.spec.ts.snap | 32 ++++++- .../__snapshots__/typescript.spec.ts.snap | 32 ++++++- .../__snapshots__/vue.spec.ts.snap | 32 ++++++- packages/webpack5/package.json | 26 ++--- packages/webpack5/src/bin/index.ts | 2 +- packages/webpack5/src/configuration/base.ts | 11 ++- packages/webpack5/src/configuration/vue.ts | 21 ++-- .../nativescript-hot-loader/hmr.runtime.ts | 96 +++++++++++++++++++ .../loaders/nativescript-hot-loader/index.ts | 13 ++- 13 files changed, 384 insertions(+), 41 deletions(-) create mode 100644 packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap index 75d9d4337..70f0e2e6a 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap @@ -11,7 +11,7 @@ exports[`angular configuration for android 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -54,6 +54,20 @@ exports[`angular configuration for android 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('js') */ { test: /\\\\.js$/, @@ -290,7 +304,7 @@ exports[`angular configuration for ios 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -333,6 +347,20 @@ exports[`angular configuration for ios 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('js') */ { test: /\\\\.js$/, diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap index 13d0dcee2..60c4ec6cf 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap @@ -11,7 +11,7 @@ exports[`base configuration for android 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -50,6 +50,20 @@ exports[`base configuration for android 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ @@ -278,7 +292,7 @@ exports[`base configuration for ios 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -317,6 +331,20 @@ exports[`base configuration for ios 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap index 3a5395cf2..7b1c239e8 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap @@ -11,7 +11,7 @@ exports[`javascript configuration for android 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -50,6 +50,20 @@ exports[`javascript configuration for android 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ @@ -315,7 +329,7 @@ exports[`javascript configuration for ios 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -354,6 +368,20 @@ exports[`javascript configuration for ios 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap index 2d3bcbb98..cffbe227b 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap @@ -11,7 +11,7 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -53,6 +53,20 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ @@ -306,7 +320,7 @@ exports[`react configuration > android > base config 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -348,6 +362,20 @@ exports[`react configuration > android > base config 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ @@ -579,7 +607,7 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -621,6 +649,20 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ @@ -875,7 +917,7 @@ exports[`react configuration > ios > base config 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -917,6 +959,20 @@ exports[`react configuration > ios > base config 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap index c70e0b169..53d6cf70c 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap @@ -11,7 +11,7 @@ exports[`svelte configuration for android 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -52,6 +52,20 @@ exports[`svelte configuration for android 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ @@ -303,7 +317,7 @@ exports[`svelte configuration for ios 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -344,6 +358,20 @@ exports[`svelte configuration for ios 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap index cf0437589..942faa244 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap @@ -11,7 +11,7 @@ exports[`typescript configuration for android 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -50,6 +50,20 @@ exports[`typescript configuration for android 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ @@ -315,7 +329,7 @@ exports[`typescript configuration for ios 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -354,6 +368,20 @@ exports[`typescript configuration for ios 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap index 3c6ef3bb2..a854dd317 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap @@ -11,7 +11,7 @@ exports[`vue configuration for android 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -53,6 +53,20 @@ exports[`vue configuration for android 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ @@ -310,7 +324,7 @@ exports[`vue configuration for ios 1`] = ` target: 'node', watchOptions: { ignored: [ - '__jest__/platforms/platforms/**', + '__jest__/platforms/**', '__jest__/App_Resources/**' ] }, @@ -352,6 +366,20 @@ exports[`vue configuration for ios 1`] = ` }, module: { rules: [ + /* config.module.rule('bundle') */ + { + enforce: 'post', + test: '__jest__/src/app.js', + use: [ + /* config.module.rule('bundle').use('nativescript-hot-loader') */ + { + loader: 'nativescript-hot-loader', + options: { + injectHMRRuntime: true + } + } + ] + }, /* config.module.rule('ts') */ { test: [ diff --git a/packages/webpack5/package.json b/packages/webpack5/package.json index dae7e9c50..1a1316905 100644 --- a/packages/webpack5/package.json +++ b/packages/webpack5/package.json @@ -23,18 +23,18 @@ "babel-loader": "^8.2.1", "chalk": "^4.1.0", "cli-highlight": "^2.1.10", - "commander": "^7.1.0", - "copy-webpack-plugin": "^8.0.0", + "commander": "^7.2.0", + "copy-webpack-plugin": "^8.1.0", "css": "^3.0.0", - "css-loader": "^5.1.1", - "dotenv-webpack": "^7.0.1", - "fork-ts-checker-webpack-plugin": "^6.1.1", + "css-loader": "^5.1.4", + "dotenv-webpack": "^7.0.2", + "fork-ts-checker-webpack-plugin": "^6.2.0", "loader-utils": "^2.0.0", "lodash.get": "^4.4.2", "micromatch": "^4.0.2", - "postcss": "^8.2.7", + "postcss": "^8.2.8", "postcss-import": "^14.0.0", - "postcss-loader": "^5.1.0", + "postcss-loader": "^5.2.0", "raw-loader": "^4.0.2", "react-refresh": "^0.9.0", "sass": "^1.32.8", @@ -42,10 +42,10 @@ "sax": "^1.2.4", "source-map": "^0.7.3", "terser-webpack-plugin": "^5.1.1", - "ts-dedent": "^2.0.0", - "ts-loader": "^8.0.17", + "ts-dedent": "^2.1.0", + "ts-loader": "^8.0.18", "vue-loader": "^15.9.5", - "webpack": "^5.24.4", + "webpack": "^5.28.0", "webpack-bundle-analyzer": "^4.4.0", "webpack-chain": "^6.5.1", "webpack-cli": "^4.5.0", @@ -56,15 +56,15 @@ }, "devDependencies": { "@types/css": "^0.0.31", - "@types/jest": "^26.0.20", + "@types/jest": "^26.0.21", "@types/loader-utils": "^2.0.1", "@types/micromatch": "^4.0.1", "@types/terser-webpack-plugin": "^5.0.2", "@types/webpack-virtual-modules": "^0.1.0", "jest": "^26.6.3", "jest-matcher-utils": "^26.6.2", - "nativescript-vue-template-compiler": "^2.8.2", - "ts-jest": "^26.5.3", + "nativescript-vue-template-compiler": "^2.8.4", + "ts-jest": "^26.5.4", "typescript": "^4.2.3" }, "peerDependencies": { diff --git a/packages/webpack5/src/bin/index.ts b/packages/webpack5/src/bin/index.ts index d06420551..797de5b75 100644 --- a/packages/webpack5/src/bin/index.ts +++ b/packages/webpack5/src/bin/index.ts @@ -1,4 +1,4 @@ -#!/bin/env node +#!/usr/bin/env node import { redBright, green, greenBright } from 'chalk'; import { program } from 'commander'; diff --git a/packages/webpack5/src/configuration/base.ts b/packages/webpack5/src/configuration/base.ts index c87869d18..f9b408863 100644 --- a/packages/webpack5/src/configuration/base.ts +++ b/packages/webpack5/src/configuration/base.ts @@ -81,7 +81,7 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { config.watchOptions({ ignored: [ - `${getProjectFilePath('platforms')}/platforms/**`, + `${getProjectFilePath('platforms')}/**`, `${env.appResourcesPath ?? getProjectFilePath('App_Resources')}/**` ] }) @@ -137,6 +137,15 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { // resolve symlinks config.resolve.symlinks(true); + config.module.rule('bundle') + .enforce('post') + .test(entryPath) + .use('nativescript-hot-loader') + .loader('nativescript-hot-loader') + .options({ + injectHMRRuntime: true + }) + // set up ts support config.module .rule('ts') diff --git a/packages/webpack5/src/configuration/vue.ts b/packages/webpack5/src/configuration/vue.ts index 9877a149a..b522b6ace 100644 --- a/packages/webpack5/src/configuration/vue.ts +++ b/packages/webpack5/src/configuration/vue.ts @@ -3,6 +3,7 @@ import { merge } from 'webpack-merge'; import Config from 'webpack-chain'; import fs from 'fs'; +import { hasDependency } from "../helpers/dependencies"; import { getPlatformName } from '../helpers/platform'; import { env as _env, IWebpackEnv } from '../index'; import { error } from "../helpers/log"; @@ -46,18 +47,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 diff --git a/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts b/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts new file mode 100644 index 000000000..a902a6b4d --- /dev/null +++ b/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts @@ -0,0 +1,96 @@ +// @ts-nocheck +// This is a runtime module - included by nativescript-hot-loader +// todo: log correct message format for CLI to pick up +// todo: build CLI service to listen for state changes +// --- +import { Http } from '@nativescript/core' + +let __NS_DEV_HOST_URL__; +Promise.race(__NS_DEV_HOST_IPS__ + .map(ip => `http://${ip}:8238/`) + .map(async url => { + await Http.request({ + method: 'get', + url + }) + + return url; + })).then(winner => { + __NS_DEV_HOST_URL__ = winner +}) + +if(module.hot) { + module.hot.dispose(() => { + console.log('Disposing entry file?!') + // require('@nativescript/core').Application.resetRootView() + }) + + const orig = global.__onLiveSync + const log = (type, info) => { + console.log(`[nds] HMR ${type}:`, info) + // console.log(__NS_DEV_HOST_IPS__[0]) + + if(__NS_DEV_HOST_URL__) { + Http.request({ + method: 'post', + url: __NS_DEV_HOST_URL__, + content: JSON.stringify({ + type, + info + }) + }).catch(err => { + console.log(err) + }) + } + } + + log('init') + + module.hot.addStatusHandler(status => { + log('status', status) + }) + + global.__onLiveSync = async function () { + // handle hot updated on LiveSync + console.log('~~~ livesynced ~~~') + + log('checking') + await module.hot.check().catch(err => { + log('checking-failed', err) + }); + log('checked') + log('applying') + await module.hot.apply({ + ignoreUnaccepted: false, + ignoreDeclined: false, + ignoreErrored: false, + + onDeclined(info) { + log('declined', info) + }, + onUnaccepted(info) { + log('unaccepted', info) + }, + onAccepted(info) { + log('accepted', info) + }, + onDisposed(info) { + log('disposed', info) + }, + onErrored(info) { + log('errored', info) + } + }).catch((err) => { + log('applying-failed', err) + }) + // log('applying') + // await module.hot.apply() + log('applying-done') + // await module.hot.apply() + setTimeout(() => { + orig(); + }); + }; + + // global.__onLiveSync() +} diff --git a/packages/webpack5/src/loaders/nativescript-hot-loader/index.ts b/packages/webpack5/src/loaders/nativescript-hot-loader/index.ts index 13d4c12bd..f9307a3aa 100644 --- a/packages/webpack5/src/loaders/nativescript-hot-loader/index.ts +++ b/packages/webpack5/src/loaders/nativescript-hot-loader/index.ts @@ -1,5 +1,6 @@ -import { relative } from "path"; +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/ @@ -11,6 +12,16 @@ export default function loader(content: string, map: any) { } 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