diff --git a/packages/webpack5/src/bin/devServer.ts b/packages/webpack5/src/bin/devServer.ts new file mode 100644 index 000000000..e60854243 --- /dev/null +++ b/packages/webpack5/src/bin/devServer.ts @@ -0,0 +1,71 @@ +import { createServer } from 'http' +// import { spawn } from 'child_process' +// yarn --cwd /Users/rigor789/Code/echo-server start + +// let statuses: { +// [hash: string]: IHMRStatusData +// } = {} + +export interface IHMRStatusData { + seq: number + uuid: string, + hash: string + status: string +} + +export function run() { + createServer((req, res) => { + if (req.url === '/ping') { + console.log('PING -> PONG!') + return res.end("Pong."); + } + + if (req.method !== 'POST') { + res.statusCode = 400; + return res.end("Unsupported method."); + } + + let data = ""; + req.on("data", chunk => { + data += chunk; + }); + + req.on("end", () => { + try { + const signal = JSON.parse(data) as IHMRStatusData; + // if (!statuses[signal.hash] || statuses[signal.hash].seq < signal.seq) { + // statuses[signal.hash] = signal + // } + if (process.send) { + process.send({ + type: 'hmr-status', + version: 1, + hash: signal.hash, + data: signal + }, (error) => { + if (error) { + console.error(`Process Send Error: `, error); + } + + return null; + }); + } + + res.end('ok.'); + } catch (e) { + res.statusCode = 400; + res.end("Invalid JSON."); + } + }); + }).listen(8238) + + // spawn('/Users/rigor789/Code/echo-server/node_modules/.bin/ts-node', ['/Users/rigor789/Code/echo-server/index.ts'], { + // cwd: '/Users/rigor789/Code/echo-server/', + // stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + // }).on('message', function (data) { + // console.log({ + // messageFromEchoServer: data + // }); + // }); +} + diff --git a/packages/webpack5/src/bin/index.ts b/packages/webpack5/src/bin/index.ts index 797de5b75..6710cf255 100644 --- a/packages/webpack5/src/bin/index.ts +++ b/packages/webpack5/src/bin/index.ts @@ -8,6 +8,7 @@ import path from 'path'; import fs from 'fs'; import { parseEnvFlags } from '../cli/parseEnvFlags'; +import { run } from "./devServer"; const defaultConfig = path.resolve( __dirname, @@ -43,9 +44,10 @@ program program .command('build') .description('Build...') - .option('--env [name]', 'environment options') - .option('--hmr', 'enable HMR') - .option('--no-hmr', 'disable HMR') + .option('--env [name]', 'environment name') + .option('--config [path]', 'config path') + // .option('--hmr', 'enable HMR') + // .option('--no-hmr', 'disable HMR') .option('--watch', 'watch for changes') .allowUnknownOption() .action((options, command) => { @@ -56,47 +58,69 @@ program if (options.env) { env['env'] = options.env; } - // const env = { - // platform: 'ios', - // verbose: true, - // appResourcesPath: 'App_Resources', - // appPath: 'src' - // } - const configPath = path.resolve(process.cwd(), 'webpack.config.js'); + const configPath = (() => { + if (options.config) { + return path.resolve(options.config); + } + + return path.resolve(process.cwd(), 'webpack.config.js') + })(); + // todo: validate config exists // todo: guard against invalid config - let configuration; + let configuration: webpack.Configuration; try { configuration = require(configPath)(env); - } catch (ignore) { - console.log(ignore); + } catch (err) { + console.log(err); } if (!configuration) { - console.log('No configuration!!!!!'); + console.log('No configuration!'); return; } const compiler = webpack(configuration); - // todo: handle --watch flag - // todo: promisify callback? - compiler.watch( - { - ignored: ['platforms'], - }, - (err, stats) => { - if (stats) { - console.log( - stats.toString({ - colors: true, - }) - ); + const webpackCompilationCallback = (err: webpack.WebpackError, stats: webpack.Stats) => { + if (err) { + // Do not keep cache anymore + compiler.purgeInputFileSystem(); + + console.error(err.stack || err); + if (err.details) { + console.error(err.details); } - // err && console.log(err) + + process.exitCode = 1; + return; } - ); + + if (stats) { + console.log( + stats.toString({ + chunks: false, + colors: true, + }) + ); + } + } + + if (options.watch) { + // run dev server + run(); + + console.log('webpack is watching the files...') + compiler.watch( + configuration.watchOptions ?? {}, + webpackCompilationCallback + ); + } else { + compiler.run( + webpackCompilationCallback + ); + } }); program.parse(process.argv); diff --git a/packages/webpack5/src/helpers/log.ts b/packages/webpack5/src/helpers/log.ts index f2dedb64f..fc95cd80a 100644 --- a/packages/webpack5/src/helpers/log.ts +++ b/packages/webpack5/src/helpers/log.ts @@ -1,4 +1,5 @@ import dedent from 'ts-dedent'; +import { env } from "@nativescript/webpack"; // de-indents strings so multi-line string literals can be used function cleanup(data: any[]) { @@ -28,7 +29,9 @@ export function warn(...data: any): void { } export function info(...data: any): void { - console.log(`[@nativescript/webpack] Info: \n`, ...cleanup(data)); + if(env.verbose) { + console.log(`[@nativescript/webpack] Info: \n`, ...cleanup(data)); + } } // todo: refine diff --git a/packages/webpack5/src/helpers/platform.ts b/packages/webpack5/src/helpers/platform.ts index 98326e9d9..2619f5287 100644 --- a/packages/webpack5/src/helpers/platform.ts +++ b/packages/webpack5/src/helpers/platform.ts @@ -1,7 +1,7 @@ import { dirname, resolve } from 'path'; import { getPackageJson, getProjectRootPath } from './project'; -import { error } from './log'; +import { error, info } from './log'; import { env } from '../'; import AndroidPlatform from '../platforms/android'; @@ -29,7 +29,7 @@ const platforms: { * @param platform A platform definition of the platform specifics */ export function addPlatform(name: string, platform: INativeScriptPlatform) { - console.log('adding platform', name, platform); + info(`Adding platform ${name}`, platform); platforms[name] = platform; } diff --git a/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts b/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts index a902a6b4d..cb1e9d26e 100644 --- a/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts +++ b/packages/webpack5/src/loaders/nativescript-hot-loader/hmr.runtime.ts @@ -3,7 +3,13 @@ // todo: log correct message format for CLI to pick up // todo: build CLI service to listen for state changes // --- -import { Http } from '@nativescript/core' + +import type { IHMRStatusData } from "../../bin/devServer"; +import { Http, Device } from '@nativescript/core' + +const uuid = Device.uuid; + +console.log(`[HMR] uuid = ${uuid}`) let __NS_DEV_HOST_URL__; Promise.race(__NS_DEV_HOST_IPS__ @@ -11,7 +17,7 @@ Promise.race(__NS_DEV_HOST_IPS__ .map(async url => { await Http.request({ method: 'get', - url + url: url + 'ping' }) return url; @@ -19,47 +25,63 @@ Promise.race(__NS_DEV_HOST_IPS__ __NS_DEV_HOST_URL__ = winner }) -if(module.hot) { +let __SEQ = 0; + +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__) { + const send = (content: object) => { + if (__NS_DEV_HOST_URL__) { Http.request({ method: 'post', url: __NS_DEV_HOST_URL__, - content: JSON.stringify({ - type, - info - }) + content: JSON.stringify(content) }).catch(err => { console.log(err) }) } } + const sendStatus = (status, hash) => { + send({ + seq: __SEQ++, + uuid, + hash, + status + } as IHMRStatusData) + } + + const orig = global.__onLiveSync + const log = (type, info) => { + // console.log(`[nds] HMR ${type}:`, info) + } + log('init') module.hot.addStatusHandler(status => { log('status', status) + // sendStatus(status) }) global.__onLiveSync = async function () { - // handle hot updated on LiveSync - console.log('~~~ livesynced ~~~') + // handle hot updates on LiveSync + console.log('~~~ livesync ~~~') log('checking') + + const hash = __webpack_require__.h(); + await module.hot.check().catch(err => { log('checking-failed', err) + sendStatus('failure', hash) }); + log('checked') log('applying') + await module.hot.apply({ ignoreUnaccepted: false, ignoreDeclined: false, @@ -67,9 +89,11 @@ if(module.hot) { onDeclined(info) { log('declined', info) + sendStatus('failure', hash); }, onUnaccepted(info) { log('unaccepted', info) + sendStatus('failure', hash); }, onAccepted(info) { log('accepted', info) @@ -79,10 +103,15 @@ if(module.hot) { }, onErrored(info) { log('errored', info) + sendStatus('failure', hash); } + }).then(() => { + sendStatus('success', hash) }).catch((err) => { + sendStatus('failure', hash) log('applying-failed', err) }) + // log('applying') // await module.hot.apply() log('applying-done') diff --git a/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts b/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts index 6b4137cce..51c9ae5c6 100644 --- a/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts +++ b/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts @@ -34,10 +34,6 @@ export class PlatformSuffixPlugin { } apply(compiler: any) { - console.log( - // this.extensions, - this.platform - ); const platformRE = new RegExp(`\.${this.platform}\.`); // require.context diff --git a/packages/webpack5/src/plugins/WatchStatePlugin.ts b/packages/webpack5/src/plugins/WatchStatePlugin.ts index f6fb2d24a..06ca2a869 100644 --- a/packages/webpack5/src/plugins/WatchStatePlugin.ts +++ b/packages/webpack5/src/plugins/WatchStatePlugin.ts @@ -1,5 +1,6 @@ const id = 'WatchStatePlugin'; const version = 1; +const DEBUG = false; export enum messages { compilationComplete = 'Webpack compilation complete.', @@ -12,8 +13,6 @@ export enum messages { * So the {N} CLI can get some idea when compilation completes. */ export class WatchStatePlugin { - isRunningWatching: boolean; - apply(compiler: any) { let isWatchMode = false; let prevAssets = []; @@ -21,8 +20,24 @@ export class WatchStatePlugin { compiler.hooks.watchRun.tapAsync(id, function (compiler, callback) { callback(); + if (isWatchMode) { + console.log(messages.changeDetected); + + if (DEBUG) { + if (compiler.modifiedFiles) { + Array.from(compiler.modifiedFiles).forEach(file => { + console.log(`MODIFIED: ${file}`) + }) + } + + if (compiler.removedFiles) { + Array.from(compiler.removedFiles).forEach(file => { + console.log(`REMOVED: ${file}`) + }) + } + } + } isWatchMode = true; - console.log(messages.changeDetected); }); compiler.hooks.afterEmit.tapAsync(id, function (compilation, callback) { @@ -53,21 +68,23 @@ export class WatchStatePlugin { notify({ type: 'compilation', version, - - emittedAssets, - staleAssets, hash: compilation.hash, + + data: { + emittedAssets, + staleAssets, + } }); }); } } function notify(message: any) { + DEBUG && console.log(`[${id}] Notify: `, message); if (!process.send) { return; } - console.log(`[${id}] Notify: `, message); process.send(message, (error) => { if (error) { console.error(`[${id}] Process Send Error: `, error);