webpack-hot-client
Advanced tools
Comparing version
65
index.js
'use strict'; | ||
const weblog = require('webpack-log'); | ||
const webpack = require('webpack'); | ||
const ParserHelpers = require('webpack/lib/ParserHelpers'); | ||
const WebSocket = require('ws'); | ||
const uuid = require('uuid/v4'); | ||
const { payload, sendStats } = require('./lib/util'); | ||
const { modifyCompiler, payload, sendStats, validateEntry } = require('./lib/util'); | ||
@@ -28,2 +25,5 @@ const defaults = { | ||
const options = Object.assign({}, defaults, opts); | ||
validateEntry(compiler); | ||
const { host, port, server } = options; | ||
@@ -54,59 +54,4 @@ const wss = new WebSocket.Server(options.server ? { server } : { host, port }); | ||
// this is how we pass the options at runtime to the client script | ||
const definePlugin = new webpack.DefinePlugin({ | ||
__hotClientOptions__: JSON.stringify(options) | ||
}); | ||
modifyCompiler(compiler, options); | ||
for (const comp of [].concat(compiler.compilers || compiler)) { | ||
const hmrPlugin = new webpack.HotModuleReplacementPlugin(); | ||
if (comp.options.target === 'web') { | ||
const { entry } = comp.options; | ||
const { name } = comp; | ||
let hotEntry = [`webpack-hot-client/client?${name || uuid()}`]; | ||
if (typeof entry === 'string' || Array.isArray(entry)) { | ||
hotEntry = hotEntry.concat(entry); | ||
} | ||
if (comp.hooks) { | ||
compiler.hooks.entryOption.call(comp.options.context, hotEntry); | ||
} else { | ||
comp.applyPluginsBailResult('entry-option', comp.options.context, hotEntry); | ||
} | ||
} | ||
log.debug('Applying DefinePlugin:__hotClientOptions__'); | ||
definePlugin.apply(comp); | ||
// fix is only available for webpack@4 | ||
if (comp.hooks) { | ||
comp.hooks.compilation.tap('HotModuleReplacementPlugin', (compilation, { | ||
normalModuleFactory | ||
}) => { | ||
const handler = (parser) => { | ||
parser.hooks.evaluateIdentifier.for('module.hot').tap({ | ||
name: 'HotModuleReplacementPlugin', | ||
before: 'NodeStuffPlugin' | ||
}, expr => ParserHelpers.evaluateToIdentifier('module.hot', !!parser.state.compilation.hotUpdateChunkTemplate)(expr)); | ||
}; | ||
normalModuleFactory.hooks.parser.for('javascript/auto').tap('HotModuleReplacementPlugin', handler); | ||
normalModuleFactory.hooks.parser.for('javascript/dynamic').tap('HotModuleReplacementPlugin', handler); | ||
}); | ||
hmrPlugin.apply(comp); | ||
} else { | ||
// must come first | ||
hmrPlugin.apply(comp); | ||
compiler.plugin('compilation', (compilation, data) => { | ||
data.normalModuleFactory.plugin('parser', (parser) => { | ||
// eslint-disable-next-line no-underscore-dangle | ||
parser._plugins['evaluate Identifier module.hot'].reverse(); | ||
}); | ||
}); | ||
} | ||
} | ||
compiler.plugin('compile', () => { | ||
@@ -113,0 +58,0 @@ stats = null; |
102
lib/util.js
'use strict'; | ||
const ParserHelpers = require('webpack/lib/ParserHelpers'); | ||
const uuid = require('uuid/v4'); | ||
const webpack = require('webpack'); | ||
const weblog = require('webpack-log'); | ||
@@ -7,4 +10,83 @@ | ||
function hotEntry(compiler) { | ||
if (compiler.options.target !== 'web') { | ||
return; | ||
} | ||
const { entry } = compiler.options; | ||
const { name } = compiler; | ||
const clientEntry = [`webpack-hot-client/client?${name || uuid()}`]; | ||
let newEntry = {}; | ||
if (typeof entry === 'object') { | ||
for (const key of Object.keys(entry)) { | ||
const value = entry[key]; | ||
if (Array.isArray(value)) { | ||
newEntry[key] = clientEntry.concat(value); | ||
} | ||
} | ||
} else { | ||
newEntry = clientEntry.concat(entry); | ||
} | ||
if (compiler.hooks) { | ||
compiler.hooks.entryOption.call(compiler.options.context, newEntry); | ||
} else { | ||
compiler.applyPluginsBailResult('entry-option', compiler.options.context, newEntry); | ||
} | ||
} | ||
function hotPlugin(compiler) { | ||
const hmrPlugin = new webpack.HotModuleReplacementPlugin(); | ||
if (compiler.hooks) { | ||
// webpack@4 | ||
// eslint-disable-next-line no-loop-func | ||
compiler.hooks.compilation.tap('HotModuleReplacementPlugin', (compilation, { | ||
normalModuleFactory | ||
}) => { | ||
const handler = (parser) => { | ||
parser.hooks.evaluateIdentifier.for('module.hot').tap({ | ||
name: 'HotModuleReplacementPlugin', | ||
before: 'NodeStuffPlugin' | ||
}, expr => ParserHelpers.evaluateToIdentifier('module.hot', !!parser.state.compilation.hotUpdateChunkTemplate)(expr)); | ||
}; | ||
normalModuleFactory.hooks.parser.for('javascript/auto').tap('HotModuleReplacementPlugin', handler); | ||
normalModuleFactory.hooks.parser.for('javascript/dynamic').tap('HotModuleReplacementPlugin', handler); | ||
}); | ||
hmrPlugin.apply(compiler); | ||
} else { | ||
// webpack < 4 | ||
// must come first | ||
hmrPlugin.apply(compiler); | ||
compiler.plugin('compilation', (compilation, data) => { | ||
data.normalModuleFactory.plugin('parser', (parser) => { | ||
// eslint-disable-next-line no-underscore-dangle | ||
parser._plugins['evaluate Identifier module.hot'].reverse(); | ||
}); | ||
}); | ||
} | ||
} | ||
module.exports = { | ||
modifyCompiler(compiler, options) { | ||
// this is how we pass the options at runtime to the client script | ||
const definePlugin = new webpack.DefinePlugin({ | ||
__hotClientOptions__: JSON.stringify(options) | ||
}); | ||
for (const comp of [].concat(compiler.compilers || compiler)) { | ||
hotEntry(comp); | ||
log.debug('Applying DefinePlugin:__hotClientOptions__'); | ||
definePlugin.apply(comp); | ||
hotPlugin(comp); | ||
} | ||
}, | ||
payload(type, data) { | ||
@@ -40,3 +122,23 @@ return JSON.stringify({ type, data }); | ||
} | ||
}, | ||
validateEntry(compiler) { | ||
for (const comp of [].concat(compiler.compilers || compiler)) { | ||
const { entry } = comp.options; | ||
const type = typeof entry; | ||
if (!Array.isArray(entry) && type !== 'object') { | ||
throw new TypeError('webpack-hot-client: The value of `entry` must be an Array or Object. Please check your webpack config.'); | ||
} | ||
if (type === 'object') { | ||
for (const key of Object.keys(entry)) { | ||
const value = entry[key]; | ||
if (!Array.isArray(value)) { | ||
throw new TypeError('webpack-hot-client: `entry` Object values must be an Array. Please check your webpack config.'); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; |
{ | ||
"name": "webpack-hot-client", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "A client for enabling, and interacting with, webpack Hot Module Replacement", | ||
@@ -18,3 +18,3 @@ "license": "MIT", | ||
"cover": "istanbul cover node_modules/mocha/bin/_mocha", | ||
"lint": "eslint index.js client.js client lib test", | ||
"lint": "eslint index.js lib test", | ||
"mocha": "mocha test/test.js --exit", | ||
@@ -21,0 +21,0 @@ "prepublishOnly": "npm run compile:client", |
25027
4.75%476
8.68%