其他 loader 配置
配置完有关 CSS loader 后,还有一个问题,我们不想将 CSS 都插入到 style 标签中,如果 CSS 样式代码很多,会导致生成的 HTML 文件很大,我们希望使用 <link>
标签引入打包后的 CSS 文件(将 CSS 单独提取出来),这时候就要使用一个插件:mini-css-extract-plugin
。
下载:yarn add mini-css-extract-plugin -D
。
配置:
代码语言:javascript复制// webpack.config.dev.js
let MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// ....
plugins: [
new MiniCssExtractPlugin({
// 抽离的样式叫什么名字(会生成在 css 文件夹下)
filename: "css/main.css",
});
],
module: {
rules: [
{
test: /.css$/,
use: [
// 将 style-loader 替换掉(不再将 css 样式放在 style 标签中)
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
}
}
eslint
eslint 是 JS 语法的校验器,它提供了一个 loader:eslint-loader
。使用之前需要先下载:yarn add eslint eslint-loader
,配置如下:
{
rules: [
{
test: /.js$/,
use: {
laoder: 'eslint-loader',
}
}
]
}
设置好 loader 后,还要在项目根目录下建一个 .eslintrc.json
文件再进行其他配置。
当然,也可以来到这个网址 https://eslint.org/demo/[1],下载默认的配置文件。下载好后把文件修改成 .eslintrc.json
名称(名称前有一个点),然后把该文件剪切到项目根目录下。
需要注意的是,loader 的执行顺序是从右到左(对于一个规则,多个 loader 的情况,配置 .css laoder 时,use 项中有多个 loader)因此,less-loader 或者 sass-loader 先执行,让代码先转成原生的 CSS,然后使用 postcss-loader 优化 CSS 属性(比如添加属性后缀),然后是 css-loader 将 CSS 文件中 import
导入的文件添加进来,最后使用 style-loader 将 CSS 样式添加到 html 的 style 标签中;从下到上(对于一个多个规则,比如同是处理 .js 文件的配置,写了好几个规则(test)),因此,eslint-loader 应该放在所有 .js 规则中的最后一个(先检验,再做别的事情)。
{
rules: [
{
test: /.js$/,
use: [
loader: "babel-loader",
]
},{
test: /.js$/,
use: [
loader: "eslint-loader",
]
}
]
}
也可以使用 options 中的 enforce 配置项:
代码语言:javascript复制{
rules: [
{
test: /.js$/,
use: [
loader: "eslint-loader",
options: {
// 强制让这个 loader 最先执行
enforce: "pre"
}
]
},{
test: /.js$/,
use: [
loader: "babel-loader",
]
}
]
}
enforce
默认值是 normal
,除了 pre
和 normal
之外,还有 post
,表示强制最后执行在 normal 之后执行这个 loader。
html-withimg-loader
当我们在 HTML 模板中有 img 标签时,img 标签的 src 的路径并不会被 webpack 转化,因此需要使用 html-withimg-loader
,使用之前同样需要先下载。然后配置:
{
rules: [
test: /.html/,
use: 'html-withimg-loader'
]
}
暴露全局变量
在 webapck 中使用 jquery 时,可以这么引入:
代码语言:javascript复制import $ from 'jquery';
但是这个 $
变量并不在全局下(window)。如果我们想要将改变量暴露到全局中,需要使用 expose-loader
。
下载:yarn add expose-loader
。将 jquery 模块暴露出来:
import $ from "expose-loader?$!jquery";
?$!
中的 $
就是指被暴露的变量名(expose-loader
?
!
是固定格式)。
当然,如果不想这么写,也可以在 rules 中进行配置:
代码语言:javascript复制{
rules: [
test: require('jquery'),
use: 'expose-loader?$'
]
}
配置好后,使用 jQuery 时,还需要进行引入:import $ from 'jquery'
。如果不想每次都引入(或说不用引入),可以使用一个插件:provide-plugin
。使用时不需要下载,webpack 自带,然后在 plugins 配置项中配置:
{
plugins: [
new webpack.ProvidePlugin({
$: 'jquery'
})
]
}
如果你在 HTML 中引入了第三方模块使用 script 标签,但在开发中如果再使用 import $ from 'jquery'
,webpack 就会多打包一次。为了不让 webpack 这样做,可以添加一个配置:
module.exports = {
plugins: [],
// ...
externals: {
jquery: 'jQuery'
}
}
使用 watch 简化操作
当代码一变化,就会自动打包。
代码语言:javascript复制// webpack.config.dev.js
module.exports = {
watch: true, // 开启监听
watchOptions: {
poll: 1000, // 每秒打包一次
// 防抖,一直输入代码,停止输入 500 毫秒后再打包。
aggreateTimeout: 500,
// 不需要进行监控的文件或目录
ignored: /node_modules/
}
}
webpack 小插件
1. cleanWebpackPlugin
该插件需要下载,功能是每次新的打包完成后,旧的打包目录会自动被删除。该插件需要传入一个参数,你要删除的路径,要删除多个目录可以传入一个数组。
2. copyWebpackPlugin
该插件需要下载。功能是将没有指定为入口的目录中的文件拷贝到打包后的目录中。 格式:
代码语言:javascript复制new CopyWebpackPlugin([
{from: '要拷贝的目录',to: '拷贝到哪里'}
]);
3. webpack.DefinePlugin
该插件是 webpack 自带的插件(不需要下载)。用它可以自定义环境变量。
代码语言:javascript复制{
plugin: [
new webpack.DefinePlugin({
// DEV 变量就是一个环境变量
DEV: JSON.stringify('dev'),
PRODUCTION: JSON.stringify('production')
}),
]
}
一般不使用这种方式配置环境变量。
4. BannerPlugin
该插件是 webpack 自带的,有一个字符串参数,表示版权说明。
代码语言:javascript复制{
plugins: [
new webpack.BannerPlugin("make 2019 by xxx"),
]
}
devServer 配置项
配置 devServer 之前需要先下载 webpack-dev-server
: yarn add webpack-dev-server -D
。
下载好之后,就可以在 webpack 配置项中去配置 webpack-dev-server 啦。
配置 devServer
devServer 的配置项很多,这里只对使用最多的做一下介绍。devserver 的配置应该是在开发环境下进行的。下面是一个简单的配置内容:
代码语言:javascript复制if(isDev){ // 如果是开发环境
config.devServer = {
// 设置 host(默认就是 localhost)
// 如果你希望服务器外部可访问
// 可以这样指定:127.0.0.1
// 即:通过 IP 地址的形式
host: "localhost",
// 设置端口号
port: "8888",
// 告诉服务器从哪个目录中提供内容
// 默认它会查找 index.html 文件作为页面根路径展示
contentBase: path.join(__dirname,"../build"),
// 这个publicPath代表静态资源的路径(打包后的静态资源路径)
publicPath: '/build/',
// 当设置成 true时,任意的 404 响应都可能需要被替代为 index.html
historyApiFallback: true,
// 是否开启 模块热替换功能
hot: true,
// 是否让浏览器自动打开(默认是 false)
open: true,
// 被作为索引文件的文件名。
// 默认是 index.html,可以通过这个来做更改
index: 'demo.html',
// 使用代理服务器
proxy: {
'/api': {
// 当请求 /api 的路径时,就是用 target 代理服务器
target: "http://loaclhost:3000",
// 重写路径
pathRewrite: {
'/api': ''
}
}
}
}
}
有时候我们不想使用代理,只是想单纯的模拟数据。就可以使用 webpack 给我们提供的一个 before
函数:
{
devServer: {
// app 参数就是 express 框架的 express 实例
before(app){
app.get('/api',(req,res) => {
// to do something...
})
}
}
}
第三种方式,就是使用 webpack 的端口(服务端和 webpack(前端) 是一个端口)在服务端需要下载一个中间件:webpack-dev-middleware
。
yarn add webpack-dev-middleware -D
然后服务端写入以下代码:
代码语言:javascript复制const express = require("express");
const webpack = require("webpack");
const webpackMiddleware = require("webpack-dev-middleware");
// 引入写好的 webpack 配置文件
let config = require("./webpack.config.js");
let compiler = webpack(config);
// 绑定中间件
app.use(webpackMiddleware(compiler));
配置命令
来到 package.json 文件中,再添加一条命令,叫做 start
,写下下面的内容:
{
"script": {
"build": "cross-env NODE_ENV='development' webpack --config config/webpack.config.dev.js",
"start": "cross-env NODE_ENV=development webpack-dev-server --config config/webpack.config.dev.js"
}
}
然后运行 npm start
就会自动打开浏览器,跳转到我们指定的 localhost:8888
端口。
有一点需要注意,在开发环境不要设置 publicPath,因为 开发环境下 devServer 执行打包的内容是在内存里的,如果设置了 publicPath 保存后页面反而不会有刷新。应在生产环境再用 publicPath。还有一点就是,每次修改配置项都要重新运行命令,这是很费时的一件事,如何在更新配置文件后不用再次重启服务呢?这在下面会说到。
historyApiFallback 更具体的配置
通过传入一个对象,比如使用 rewrites 这个选项,可进一步地控制。
代码语言:javascript复制{
devServer: {
historyApiFallback: {
// 是个数组
rewrites: [
// b=表示 以 “/” 请求的页面,会返回 这个路径下的 html 文件
{ from: /^/$/, to: '/views/landing.html' },
{ from: /^/subpage/, to: '/views/subpage.html' },
// 别的则会返回 404 页面
{ from: /./, to: '/views/404.html' }
]
}
}
}
devServer 中 publicPath 的配置
devServer 中的 publicPath 与 output 中的并不同。devServer 中的 publicPath 指的是 webpack-dev-server 的静态资源服务路径。假如我们打包的内容在 build 文件夹下,则 publicPath 应是 /build/
,这里有个技巧,output 中指定的打包路径,比如:path: path.join(__dirname,'../build')
那么 devServer 的 publicPath 一般就是 join 方法中的那个 build
。如果指定别的路径,很可能就会访问不到资源。
开启模块热替换功能
开启这个功能可以让我们修改文件并保持后,页面不会出现刷新的情况,页面中的内容是被动态更替了!这样减少了页面重新绘制的时间。在 devServer 中单纯的让 hot = true 是没有作用的,还需要一个 webpack 插件。这个插件是 webpack 内置的插件,不需要下载。具体配置步骤如下:
代码语言:javascript复制/**
* 来到 webpack 配置文件
* 引入 热更替插件
*/
const webpack = require('webpack');
// 来到 devServer 选项
{
devServer: {
hot: true
},
// 添加 plugin
plugins: [
new webpack.HotModuleReplacementPlugin(),
]
}
配置好 webpack 之后,还需要在入口程序处检测 module.hot 是否存在(这个对象是在 webpack 打包后自动加入的)。 假如我们的程序入口文件是 index.js,可以这么来写:
代码语言:javascript复制// index.js
if(module.hot){
// 调用 accept 方法开启热更替
module.hot.accept();
}
上面步骤做完后,就可以使用 热更替了。如果有多个页面,则应为每个页面的入口作检验。
React 中使用热模块更替
在 React 中,index.js 常常做程序的入口,而 App.js 往往需要 index.js 的导入。module.hot.accept
方法可以接收两个参数,一个是路径字符串或者数组,另一个是回调函数。在 index.js 中可以这么来写:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
function render(){
ReactDOM.render(
<App />,
document.getElementById('root')
)
}
render();
if (module.hot) {
console.log(module.hot);
// 当第一个参数是数组时
// 表示 有多个路径需要热模块更替
// 回调用于在模块更新后触发的函数
module.hot.accept('./App.jsx',() => {
render();
});
}
React 自己来提供了一个官方的热更替模块 —— react-hot-loader
。使用它时需要下载: npm install react-hot-loader
。使用时也需要配置。
- 首先需要配置 webpack 文件:
// 更改 entry:
{
entry: ['react-hot-loader/patch', '../src/index.js'],
}
- 然后来到
.babelrc
文件,添加一个 plugin:
{
"plugins": ["react-hot-loader/babel"]
}
- 来到 index.js 文件处,你就可以直接把原来判断 module.hot 的内容给删掉了。而且 webpack 配置文件也不需要再引入 热更新插件(恢复没有热更新配置时的样子,但是 hot 项不要变成 false)。
- 来到 App.js 文件,更改内容:
import { hot } from 'react-hot-loader';
function App(){
// ....
}
// 最后这样导出:
export default hot(module)(App);
还没完,还应该重新下载一个包:yarn add @hot-loader/react-dom
这个包和 react-dom
一样,只是它有热替换功能。下载之后,在 webpack resolve 配置项中写入:
alias: {
// 这样,你在引入 react-dom 时,就会引入这个包
'react-dom': '@hot-loader/react-dom'
}
最后,重启服务,热更替模块就可以用了。使用 react-hot-loader 的好处就是,可以避免 React 组件的不必要渲染。
下一节将介绍 webpack 优化、代码分片与压缩,以及改造 create-react-app 的 webpack 配置,让其支持多页应用。
参考资料
[1]
eslint 配置文件下载: https://eslint.org/demo/