webpck-dev-ser作为开发时的服务非常方便,本文将对webpack-dev-server从原理层次解析,来看看它是如何实现服务和热更新的。
1,webpack-dev-server的服务原理
基于express,搭建了一个http服务,根据路由返回不同的内容
2,静态资源服务
webpack-dev-server使用了webpack-dev-middleware,改变了webpack打包的输出地址,使用了memory-fs模块将打包资源输出到内存中;
基于内存中的文件,根据路径,express使用中间件static搭建了静态文件服务器;
监控:用chokidar来监视文件变化, server的内部维护的有一个socket集合
3,单页面应用路由对应
使用了包connect-history-api-fallback,该包也是一个express中间件,用来对资源重定向
对于配置:
historyApiFallback可以配置为true和对象
实例:
var server = new WebpackDevServer(compiler, {
//contentBase:path.resolve(__dirname,"/build"),
publicPath: config.output.publicPath,
// hot: true,
headers: {
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
},
historyApiFallback: true,
stats: {
colors: true // 用颜色标识
},
// progress: true,
port: 9000,
overlay: {
warnings: true,
errors: true
},
noInfo: false
});
server.listen(9000, 'localhost', (err, res) => {
if (err) {
return console.log(err)
}
console.log('webpack 9000已启动...')
});
4,contentBase,contentBasePublicPath, transportMode等默认参数的设置
在utils/normalizeOptions.js文件中设置:
源码:
function normalizeOptions(compiler, options) {
// Setup default value
options.contentBase =
options.contentBase !== undefined ? options.contentBase : process.cwd();
// Setup default value
options.contentBasePublicPath = options.contentBasePublicPath || '/';
// normalize transportMode option
if (options.transportMode === undefined) {
options.transportMode = {
server: 'sockjs',
client: 'sockjs',
};
} else {
switch (typeof options.transportMode) {
case 'string':
options.transportMode = {
server: options.transportMode,
client: options.transportMode,
};
break;
// if not a string, it is an object
default:
options.transportMode.server = options.transportMode.server || 'sockjs';
options.transportMode.client = options.transportMode.client || 'sockjs';
}
}
if (!options.watchOptions) {
options.watchOptions = {};
}
}
上面代码设置了webpack-dev-server一些重要参数,了解默认参数,有助于了解options选项的含义
5,比较重要的options.publicPath
publicPath在源码中有两个地方用到
分别是:routes.js,createConfig
部分源码:
if (!options.publicPath) {
// eslint-disable-next-line
options.publicPath =
(firstWpOpt.output && firstWpOpt.output.publicPath) || '';
if (
!isAbsoluteUrl(String(options.publicPath)) &&
options.publicPath[0] !== '/'
) {
options.publicPath = `/${options.publicPath}`;
}
}
app.get('/webpack-dev-server', (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.write(
''
);
const outputPath = middleware.getFilenameFromUrl(options.publicPath || '/');
const filesystem = middleware.fileSystem;
writeDirectory(options.publicPath || '/', outputPath);
res.end('');
function writeDirectory(baseUrl, basePath) {
const content = filesystem.readdirSync(basePath);
res.write('');
content.forEach((item) => {
const p = `${basePath}/${item}`;
if (filesystem.statSync(p).isFile()) {
res.write(`- ${item}
`);
if (/\.js$/.test(item)) {
const html = item.substr(0, item.length - 3);
const containerHref = baseUrl + html;
const magicHtmlHref =
baseUrl.replace(
// eslint-disable-next-line
/(^(https?:\/\/[^\/]+)?\/)/,
'$1webpack-dev-server/'
) + html;
res.write(
`- ${html}` +
` (magic html for ${item}) (webpack-dev-server)` +
`
`
);
}
} else {
res.write(`- ${item}
`);
writeDirectory(`${baseUrl + item}/`, p);
res.write(' ');
}
});
res.write('
');
}
});
publicPath指定了静态资源, html文件的路径
6,webpack配置中的output.publicPath和webpack-dev-server中的publicPath的区别
output.publicPath是指插入到html文件中静态资源路径的统一前缀
实例:
output: {
path: path.resolve(__dirname, '../build'),
publicPath: 'http://localhost:9000/a',
filename: 'js/[name].js',
chunkFilename: 'js/[name].chunk.js',
hotUpdateChunkFilename: '[id].[hash].hot-update.js',
hotUpdateMainFilename: '[hash].hot-update.json'
},
这时候在浏览器中:
webpack-dev-server中的publicPath规定了静态资源的路径,比如:
new WebpackDevServer(compiler, {
//contentBase:path.resolve(__dirname,"/build"),
publicPath:'http://localhost:9000/abc/',
// hot: true,
headers: {
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
}
}
这时候的静态资源路径统一前缀是/abc,比如要访问a.js文件,就需要访问:
http://localhost:9000/abc/js/a.js
所以上面两种publicPath的配置是不合理的,会出现html中的资源访问不到静态资源,所以两种publicPath应该设置一致,比如可以设置:
new WebpackDevServer(compiler, {
//contentBase:path.resolve(__dirname,"/build"),
publicPath: config.output.publicPath,
// hot: true,
}
7,多文件配置实例,主要原理是资源重定向
var server = new WebpackDevServer(compiler, {
//contentBase:path.resolve(__dirname,"/build"),
publicPath: config.output.publicPath,
// hot: true,
headers: {
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
},
historyApiFallback: {
rewrites:[
{from:/^\/a/,to:'/a.html'}, //以根‘/a’开始,以根‘/’结尾的请求,重定向到‘a.html’
{from:/^\/b/,to:'/b.html'}, //以‘/b’开始的请求,重定向到‘b.html’
{from:/./,to:'/views/404.html'} //不匹配上面的任意除了换行符之外的请求,重定向到‘404.html’
]
},
stats: {
colors: true // 用颜色标识
},
// progress: true,
port: 9000,
overlay: {
warnings: true,
errors: true
},
noInfo: false
});
server.listen(9000, 'localhost', (err, res) => {
if (err) {
return console.log(err)
}
console.log('webpack 9000已启动...')
});
其余的基本配置不太复杂,不再一一介绍
多页面应用渲染实例:多页面集成单页面应用实例