diff --git a/examples/plugin/wpackio.server.js b/examples/plugin/wpackio.server.js index 79adc7d22..42361257f 100644 --- a/examples/plugin/wpackio.server.js +++ b/examples/plugin/wpackio.server.js @@ -1,14 +1,13 @@ module.exports = { // Your LAN IP or host where you would want the live server // Override this if you know your correct external IP (LAN) - // Otherwise, the system will always use localhost and will not - // work for external IP. + // Otherwise, the system will always try to get a LAN ip. // This will also create some issues with file watching because for // some reason, service-worker doesn't work on localhost? // https://github.com/BrowserSync/browser-sync/issues/1295 // So it is recommended to change this to your LAN IP. // If you intend to access it from your LAN (probably do?) - host: '192.168.1.144', + host: undefined, // Your WordPress development server address proxy: 'http://localhost:8080', // PORT on your localhost where you would want live server to hook diff --git a/packages/scripts/@types/dev-ip.d.ts b/packages/scripts/@types/dev-ip.d.ts new file mode 100644 index 000000000..430b45e58 --- /dev/null +++ b/packages/scripts/@types/dev-ip.d.ts @@ -0,0 +1,3 @@ +declare module 'dev-ip' { + export default function devIp():string[]|false; +} diff --git a/packages/scripts/@types/react-dev-utils/formatWebpackMessages.d.ts b/packages/scripts/@types/react-dev-utils/formatWebpackMessages.d.ts new file mode 100644 index 000000000..e42b22726 --- /dev/null +++ b/packages/scripts/@types/react-dev-utils/formatWebpackMessages.d.ts @@ -0,0 +1,4 @@ +declare module 'react-dev-utils/formatWebpackMessages' { + import * as webpack from 'webpack'; + export default function formatWebpackMessages(message:any, isError?:boolean): {errors:string[], warnings:string[]} +} diff --git a/packages/scripts/@types/react-dev-utils/openBrowser.d.ts b/packages/scripts/@types/react-dev-utils/openBrowser.d.ts new file mode 100644 index 000000000..fc6335ef4 --- /dev/null +++ b/packages/scripts/@types/react-dev-utils/openBrowser.d.ts @@ -0,0 +1,3 @@ +declare module 'react-dev-utils/openBrowser' { + export default function openBrowser(url:string):void; +} diff --git a/packages/scripts/package.json b/packages/scripts/package.json index e8c1cd544..d60f3a7b5 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -36,6 +36,7 @@ "clean-webpack-plugin": "^0.1.19", "commander": "^2.18.0", "css-loader": "^1.0.0", + "dev-ip": "^1.0.1", "file-loader": "^2.0.0", "mini-css-extract-plugin": "^0.4.3", "optimize-css-assets-webpack-plugin": "^5.0.1", diff --git a/packages/scripts/src/config/CreateWebpackConfig.ts b/packages/scripts/src/config/CreateWebpackConfig.ts index 36995e033..1797bc1ee 100644 --- a/packages/scripts/src/config/CreateWebpackConfig.ts +++ b/packages/scripts/src/config/CreateWebpackConfig.ts @@ -71,7 +71,7 @@ export class CreateWebpackConfig { // If the configuration is for multiple compiler mode // Then return an array of config. - if (this.projectConfig.files.length > 1) { + if (this.isMultiCompiler()) { // Return an array of configuration const config: webpack.Configuration[] = []; this.projectConfig.files.forEach((file: FileConfig) => { @@ -85,6 +85,13 @@ export class CreateWebpackConfig { return this.getSingleWebpackConfig(this.projectConfig.files[0]); } + /** + * Is the config going to be for multi-compiler? + */ + public isMultiCompiler(): boolean { + return this.projectConfig.files.length > 1; + } + /** * Get devServer publicPath for all sorts of middlewares. */ @@ -100,6 +107,13 @@ export class CreateWebpackConfig { return `${this.publicPath}__wpackio`; } + /** + * Get Url to publicPath. + */ + public getPublicPathUrl(): string { + return `${this.getServerUrl()}${this.publicPath}`; + } + /** * Get server URL where the hot server is live and waiting to become * awesome. @@ -107,7 +121,7 @@ export class CreateWebpackConfig { public getServerUrl(): string { const { host, port } = this.serverConfig; - return `//${host || 'localhost'}:${port}${this.publicPath}`; + return `//${host || 'localhost'}:${port}`; } /** @@ -143,7 +157,7 @@ export class CreateWebpackConfig { optimizeSplitChunks, outputPath, publicPath: this.getPublicPath(), - serverUrl: this.getServerUrl(), + publicPathUrl: this.getPublicPathUrl(), }, this.cwd, this.isDev diff --git a/packages/scripts/src/config/WebpackConfigHelper.ts b/packages/scripts/src/config/WebpackConfigHelper.ts index d356e546e..41105f3bc 100644 --- a/packages/scripts/src/config/WebpackConfigHelper.ts +++ b/packages/scripts/src/config/WebpackConfigHelper.ts @@ -29,7 +29,7 @@ export interface WebpackConfigHelperConfig { alias?: ProjectConfig['alias']; optimizeSplitChunks: ProjectConfig['optimizeSplitChunks']; publicPath: string; - serverUrl: string; + publicPathUrl: string; } interface CommonWebpackConfig { @@ -186,7 +186,7 @@ export class WebpackConfigHelper { // That URL of the proxied server starts from root? // Maybe we can have a `prefix` in Config, but let's not do that // right now. - output.publicPath = this.config.serverUrl; + output.publicPath = this.config.publicPathUrl; } return output; @@ -204,7 +204,10 @@ export class WebpackConfigHelper { 'process.env.BABEL_ENV': JSON.stringify(this.env), }), // Clean dist directory - new cleanWebpackPlugin([this.outputPath], { root: this.cwd }), + new cleanWebpackPlugin( + [`${this.outputPath}/${this.outputInnerDir}`], + { root: this.cwd, verbose: false } + ), // Initiate mini css extract new miniCssExtractPlugin({ filename: `${this.outputInnerDir}/[name].css`, diff --git a/packages/scripts/src/scripts/Server.ts b/packages/scripts/src/scripts/Server.ts index d8ac528e1..2839e0a2b 100644 --- a/packages/scripts/src/scripts/Server.ts +++ b/packages/scripts/src/scripts/Server.ts @@ -1,4 +1,7 @@ import browserSync from 'browser-sync'; +import devIp from 'dev-ip'; +import formatWebpackMessages from 'react-dev-utils/formatWebpackMessages'; +import openBrowser from 'react-dev-utils/openBrowser'; import webpack from 'webpack'; import webpackDevMiddleware from 'webpack-dev-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware'; @@ -24,6 +27,9 @@ export class Server { private bs?: browserSync.BrowserSyncInstance; private devMiddlewares?: webpackDevMiddleware.WebpackDevMiddleware[]; + private webpackConfig: CreateWebpackConfig; + private isBrowserOpened: boolean = false; + /** * Create an instance. * @@ -38,6 +44,20 @@ export class Server { this.projectConfig = projectConfig; this.serverConfig = serverConfig; this.cwd = cwd; + // Override serverConfig host if it is undefined + if (!this.serverConfig.host) { + const possibleHost = devIp(); + if (possibleHost) { + this.serverConfig.host = possibleHost[0]; + } + } + // Create the webpackConfig + this.webpackConfig = new CreateWebpackConfig( + this.projectConfig, + this.serverConfig, + this.cwd, + true + ); } /** @@ -52,33 +72,38 @@ export class Server { } // Create browserSync Instance const bs = browserSync.create(); - // Create configuration - const webpackConfig = new CreateWebpackConfig( - this.projectConfig, - this.serverConfig, - this.cwd, - true - ); + // Init middleware and stuff const middlewares: browserSync.MiddlewareHandler[] = []; const devMiddlewares: webpackDevMiddleware.WebpackDevMiddleware[] = []; // We can have multi-compiler or single compiler, depending on the config // we get. And both of them works for dev and hot middleware. - const compiler = webpack( - webpackConfig.getWebpackConfig() as webpack.Configuration - ); + let compiler: webpack.Compiler | webpack.MultiCompiler; + if (this.webpackConfig.isMultiCompiler()) { + compiler = webpack( + this.webpackConfig.getWebpackConfig() as webpack.Configuration[] + ); + } else { + compiler = webpack( + this.webpackConfig.getWebpackConfig() as webpack.Configuration + ); + } + + // tslint:disable:no-object-literal-type-assertion const devMiddleware = webpackDevMiddleware(compiler, { - stats: { colors: true }, - publicPath: webpackConfig.getPublicPath(), - }); + stats: false, + publicPath: this.webpackConfig.getPublicPath(), + logLevel: 'silent', + logTime: false, + } as webpackDevMiddleware.Options); const hotMiddleware = webpackHotMiddleware(compiler, { // Now because we are already using publicPath(dynamicPublicPath = true) in client // we have to assume that it is prefixed. That's why we prefix it in the server too. // Because it could be multi-compiler, I guess it will just work fine since we are // passing in the `name` too. - path: `${webpackConfig.getHmrPath()}`, + path: `${this.webpackConfig.getHmrPath()}`, }); // Push them middlewares.push(devMiddleware); @@ -89,7 +114,7 @@ export class Server { bs.init({ // We need to silent browserSync, otherwise might conflict with // webpack-dashboard - logLevel: 'warn', + logLevel: 'silent', port: this.serverConfig.port, ui: this.serverConfig.ui, proxy: { @@ -98,10 +123,22 @@ export class Server { // Middleware for webpack hot reload middleware: middlewares, host: this.serverConfig.host, - open: 'external', // We don't want to open right away + open: false, // We don't want to open right away notify: this.serverConfig.notify, }); + // Now open the browser, when first compilation is done + const isBrowserOpen = false; + if ((compiler as webpack.MultiCompiler).compilers) { + // Apply hooks on each one + (compiler as webpack.MultiCompiler).compilers.forEach( + this.addHooks + ); + } else { + // Apply hook on the single one + this.addHooks(compiler as webpack.Compiler); + } + // Watch for user defined files, when it changes, reload // When that change, reload if (this.projectConfig.watch) { @@ -119,6 +156,52 @@ export class Server { this.devMiddlewares = devMiddlewares; } + /** + * Add hooks to compiler instances. + */ + public addHooks = (compiler: webpack.Compiler): void => { + const { done, afterEmit } = compiler.hooks; + const serverUrl = this.webpackConfig.getServerUrl(); + + // Open browser on last emit of first compilation + // Q: How do I know this is the last emit, for multi-compiler + // Maybe I can count? + afterEmit.tap('wpackio-hot-server', () => { + if (!this.isBrowserOpened) { + // tslint:disable:no-http-string + console.log(`opening url http:${serverUrl}`); + openBrowser(`http:${serverUrl}`); + this.isBrowserOpened = true; + } + }); + + // Show stats + done.tap('wpackio-hot-server', stats => { + return; + + const raw = stats.toJson('verbose'); + const messages = formatWebpackMessages(raw); + if (!messages.errors.length && !messages.warnings.length) { + // console.log( + // stats.toString({ + // colors: true, + // modules: false, + // chunks: false, + // }) + // ); + } + if (messages.errors.length) { + console.log('Failed to compile.'); + messages.errors.forEach(e => console.log(e)); + + return; + } + if (messages.warnings.length) { + console.log('Compiled with warnings.'); + messages.warnings.forEach(w => console.log(w)); + } + }); + }; /** * Stop the server and clean up all processes. */