前言
上一篇文章介绍了如何打包成 HTML 文件,接着我们将介绍如何搭建开发环境之热更新。
正文
使用 source-map
如果按照默认的 production 模式打包代码是,可能会很难追踪错误和警告在源代码中的原始位置。例如,如果将三个源文件(a.js
, b.js
和 c.js
)打包到一个 bundle(bundle.js)
中,而其中一个源文件包含一个错误,那么堆栈跟踪就会直接指向到 bundle.js
。你可能需要准确地知道错误来自于哪个源文件,所以这种提示这通常不会提供太多帮助。
为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps 功能,可以将编译后的代码映射回原始源代码。如果一个错误来自于 b.js
,source map 就会明确的告诉你。
source map 有许多可用选项,请务必仔细阅读它们,以便可以根据需要进行配置。
对于本指南,我们将使用 eval-source-map
选项,这有助于解释说明示例意图(此配置仅用于示例,不要用于生产环境)
选择一个工具
在每次编译代码时,手动运行 yarn run build
会显得特别麻烦。
webpack 提供了几种可选方式,帮助我们在代码发生变化后自动编译代码:
- webpack watch mode(webpack 观察模式)
- webpack-dev-server
- webpack-dev-middleware
webpack 可以在 watch mode
(观察模式)下使用。在这种模式下,webpack 将监视您的文件,并在更改时重新编译。
webpack-dev-server
提供了一个易于部署的开发服务器,具有快速的实时重载(live reloading)功能。
如果你已经有一个开发服务器并且需要完全的灵活性,可以使用 webpack-dev-middleware
作为中间件。
webapck-dev-server
和 webpack-dev-middleware
使用内存编译,这意味着 bundle
不会被保存在硬盘上。这使得编译十分迅速,并导致你的文件系统更少麻烦。
在大多数情况下你会想要使用 webpack-dev-server
,因为这是最简单的开始的方式,并且提供了很多开箱即用的功能。本项目中也将会使用到它。
简单配置 webpack-dev-server
# 安装 $ yarn add webpack-dev-server@3.9.0 --dev // 修改 webpack.config.js 配置 devServer: { contentBase: './dist', open: true } // 在 package.json 添加 npm script "scripts": { "dev": "webpack-dev-server --config webpack.config.js --colors" }
以上配置告知 webpack-dev-server
,将 dist
目录下的文件 serve
到 localhost:8080
下。(译注:serve
,将资源作为 server
的可访问文件)。
现在,在命令行中运行 yarn run dev
,我们会看到浏览器自动加载页面。如果你更改任何源文件并保存它们,web server 将在编译代码后自动重新加载。
但是同时可以观察到一个细节,每次更改文件页面会重新加载,但是这应该不是我们想要的,我们想要的是模块热替换(hot module replacement
)。
HMR 模块热替换配置
*首先它不适用于生产环境,仅应用于开发环境。
模块热替换功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
- 保留在完全重新加载页面期间丢失的应用程序状态。
- 只更新变更内容,以节省宝贵的开发时间。
- 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
// webpack.config.js const config = { // ... 其他已有配置不变 devServer: { contentBase: './dist', hot: true, open: true }, plugins: [ // 在 webpack 4 中其实已被弃用,取代它的是 optimization.namedModules 后续的文章会讲解到 // new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin() ] }
我们在入口文件 index.js
引入 main.js
。
// main.js console.log('This is main.js')
我们来修改 index.js
文件,以便当 main.js
内部发生变更时可以告诉 webpack 接受更新的模块。
// index.js import './main.js' console.log('Hello Webpack!') if(module.hot) { module.hot.accept('./main.js', () => { console.log('Accept update!') }) }
注意,若修改了 webpack 配置文件,我们需要重新执行
yarn run dev
使其生效。
当我们修改 main.js
按下保存之后,会看到浏览器控制台会输出以下信息:
[WDS] App updated. Recompiling... [WDS] App hot update... [HMR] Checking for updates on the server... This is main.js. We modified the main.js file. Accept update! [HMR] Updated modules: [HMR] - ./src/main.js [HMR] App is up to date.
HMR 加载样式
借助于 style-loader
,使用模块热替换来加载 CSS 实际上极其简单。此 loader 在幕后使用了 module.hot.accept
,在 CSS 依赖模块更新之后,会将其 patch(修补) 到 <style>
标签中。
在此之前,我们说过 webpack 只能读取 JavaScript 和 JSON 文件,其他类型的文件需要借助对应的 loader 来处理。
首先使用以下命令安装两个 loader 。
$ yarn add --dev css-loader@3.2.0 style-loader@1.0.0
更新 webpack 配置文件
// webpack.config.js const config = { module: { rules: [ { test: /\.css/, use: ['style-loader', 'css-loader'] } ] } } module.exports = config
我们在 src
目录下新增 style.css
文件,并在 index.js
中引入。
/* style.css */ body { font-size: 30px; }
// index.js import './main.js' import './style.css' console.log('Hello Webpack!') if(module.hot) { module.hot.accept('./main.js', () => { console.log('Accept update!') }) }
然后我们试着修改 style.css
的样式,就能看到页面字体大小随之更改,而无需完全刷新。
至此
我们最简单的 HMR 已经配置好了,接着我们将会介绍接入 React 。
最后附上
// webpack.config.js const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const { CleanWebpackPlugin } = require('clean-webpack-plugin') const webpack = require('webpack') const config = { mode: 'development', devtool: 'eval-source-map', entry: { index: path.resolve(__dirname, './src/index.js') }, devServer: { contentBase: './dist', hot: true, open: true }, plugins: [ new HtmlWebpackPlugin({ title: '开发环境', // 模板要使用 <title><%= htmlWebpackPlugin.options.title %></title> 配置才生效 template: './src/index.html', // 模板路径 filename: 'index.html', // 输出 HTML 文件名称 inject: 'body', // 插入的 script 标签位于 body 底部,true 同理 hash: true, // 加上 hash 值 favicon: './src/favicon.ico' }), // 新版无需再指定删除目录,默认删除 output 目录 new CleanWebpackPlugin(), // 热更新 new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin() ], module: { rules: [ { test: /\.css/, use: ['style-loader', 'css-loader'] } ] } } module.exports = config
// index.js import './main.js' import './style.css' console.log('Hello Webpack!') if(module.hot) { module.hot.accept('./main.js', () => { console.log('Accept update!') }) }
// 当前项目目录 webpack4_demo | - /assets | - /config | - /dist | - some outputh file | - /src | - favicon.ico | - index.html | - index.js | - main.js | - styles.css | - .gitignore | - package.json | - README.md | - webpack.config.js | - yarn.lock