前言
之前一段时间工作原因把精力都放在小程序上,趁现在有点空闲时间,刚好官方文档也补充完整了,我准备重温一下 webpack 之路了,因为官方文档已经写得非常详细,我会大量引用原文描述,主要重点放在怎么从零构建 webpack4 代码上,这不是一个系统的教程,而是从零摸索一步步搭建起来的笔记,所以前期可能bug会后续发现继续修复而不是修改文章.
系列文章
webpack4从零开始构建(一)
webpack4+React16项目构建(二)
webpack4功能配置划分细化(三)
webpack4引入Ant Design和Typescript(四)
webpack4代码去重,简化信息和构建优化(五)
webpack4配置Vue版脚手架(六)
基本已经可以使用的完整配置webpack4_demo,
继续上回分解,我们之前已经实现了资源处理,配置环境分开,引入React库和babel库,图片优化和打包可视化,这一章我们就将零散的文件进一步规格化配置
2018/12/26上传,代码同步到第四篇文章
2019/03/14上传,补充代码到第三篇文章
配置文件
我们在根目录单独新建文件夹config
,将所有webpack配置文件放进去,然后改一下相对路径的引入,接下来抽取出些配置文件单独一个模块管理.
alias.js
路径简化单独一个配置文件方便查找
const path = require('path');
// 创建 import 或 require 的别名,来确保模块引入变得更简单
module.exports = {
"@": path.resolve(__dirname, "../src/"),
IMG: path.resolve(__dirname, "../src/img"),
STYLE: path.resolve(__dirname, "../src/style"),
JS: path.resolve(__dirname, "../src/js"),
ROUTER: path.resolve(__dirname, "../src/router"),
PAGE: path.resolve(__dirname, "../src/page"),
CMT: path.resolve(__dirname, "../src/component")
};
rules.js
规则处理单独一个模块,实在太多了
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = [
{
test: /\.(js|jsx)$/, // 匹配文件
exclude: /node_modules/, // 过滤文件夹
use: {
loader: "babel-loader"
}
},
{
test: /\.s?css$/, // 匹配文件
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
publicPath: "../"
}
},
// "style-loader", // 使用<style>将css-loader内部样式注入到我们的HTML页面
"css-loader", // 加载.css文件将其转换为JS模块
"sass-loader" // 加载 SASS / SCSS 文件并将其编译为 CSS
]
},
{
test: /\.(html)$/,
use: {
loader: "html-loader",
options: {
attrs: ["img:src", "img:data-src", "audio:src"],
minimize: true
}
}
},
{
test: /\.(png|svg|jpe?g|gif)$/i, // 图片处理
use: [
{
loader: "url-loader",
options: {
name: "[name].[hash:5].[ext]",
limit: 20 * 1024, // size <= 50kb
outputPath: "img"
}
},
{
loader: "image-webpack-loader",
options: {
// Compress JPEG images
mozjpeg: {
progressive: true,
quality: 65
},
// Compress PNG images
optipng: {
enabled: false
},
// Compress PNG images
pngquant: {
quality: "65-90",
speed: 4
},
// Compress GIF images
gifsicle: {
interlaced: false
},
// Compress JPG & PNG images into WEBP
webp: {
quality: 75
}
}
}
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/, // 字体处理
use: ["file-loader"]
},
{
test: /\.xml$/, // 文件处理
use: ["xml-loader"]
}
];
webpack.common.js
现在改改路径和引入,瞬间清爽很多,有个地方需要注意的是现在配置文件和dist文件不在同一个层级,默认是不允许删除层级之上,我们需要开放权限
因为最近更新版本不支持以前写法,所以替换一下
// 清除文件
new CleanWebpackPlugin({
dangerouslyAllowCleanPatternsOutsideProject: true,
cleanOnceBeforeBuildPatterns: ["../dist"],
dry: true
}),
const path = require("path"),
HtmlWebpackPlugin = require("html-webpack-plugin"),
CleanWebpackPlugin = require("clean-webpack-plugin"),
MiniCssExtractPlugin = require("mini-css-extract-plugin"),
alias = require("./alias"),
rules = require("./rules");
module.exports = {
// 入口
entry: "./src/index.js",
// 输出
output: {
// 打包文件名
filename: "[name].bundle.js",
// 输出路径
path: path.resolve(__dirname, "../dist"),
// 资源请求路径
publicPath: ""
},
module: {
rules
},
plugins: [
// 清除文件
new CleanWebpackPlugin({
dangerouslyAllowCleanPatternsOutsideProject: true,
cleanOnceBeforeBuildPatterns: ["../dist/**"],
dry: false
}),
// 提取样式文件
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "style/[name].[chunkhash:8].css",
chunkFilename: "style/[id].css"
}),
new HtmlWebpackPlugin({
// title
title: "test",
// 模板
template: "index.html"
})
],
resolve: {
// 创建 import 或 require 的别名,来确保模块引入变得更简单
alias
}
};
package.json
也稍微改一下执行路径,换个更加合适的命令名
"scripts": {
"dev": "webpack --config ./config/webpack.dev.js",
"prod": "webpack --config ./config/webpack.prod.js",
"server": "webpack-dev-server --config ./config/webpack.server.js"
},
为了实现配置效果我们需要安装一个插件cross-env
yarn add cross-env
这是一个可以跨平台系统设置环境变量的库,简单来说就是在命令行设置变量
"scripts": {
"dev": "cross-env NODE_ENV=DEV webpack --config ./config/webpack.dev.js",
"prod": "cross-env NODE_ENV=PROD webpack --config ./config/webpack.prod.js",
"server": "cross-env NODE_ENV=SERVER webpack-dev-server --config ./config/webpack.server.js"
},
然后我们就能在js里获取process.env.NODE_ENV
字段拿到我们自定义的字段了.接下来我们修改一下配置文件
图片配置
我们目前的图片配置分别使用了url-loader
转码和image-webpack-loader
做压缩,实际开发中我们不需要压缩,所以将后者抽离.
{
test: /\.(png|svg|jpe?g|gif)$/i, // 图片处理
use:
process.env.NODE_ENV === "PROD"
? [
{
loader: "url-loader",
options: {
name: "[name].[hash:5].[ext]",
limit: 20 * 1024, // size <= 50kb
outputPath: "img"
}
},
{
loader: "image-webpack-loader",
options: {
// Compress JPEG images
mozjpeg: {
progressive: true,
quality: 65
},
// Compress PNG images
optipng: {
enabled: false
},
// Compress PNG images
pngquant: {
quality: "65-90",
speed: 4
},
// Compress GIF images
gifsicle: {
interlaced: false
}
}
}
]
: [
{
loader: "url-loader",
options: {
name: "[name].[hash:5].[ext]",
limit: 20 * 1024, // size <= 50kb
outputPath: "img"
}
}
]
},
REACT热更新
引入react之后会发现现在修改js代码会刷新,但是浏览器需要手动刷新才看到效果,控制台提示
Ignored an update to unaccepted module,The following modules couldn’t be hot updated: (They would need a full reload!)
这个问题我们可以通过引入react-hot-loader解决
yarn add react-hot-loader
先在.babelrc
添加配置
{
"presets": [
["env", {
modules: false
}], "react"
],
"plugins": ["react-hot-loader/babel"]
}
然后把根组件包裹在里面输出,修改\src\page\main.jsx
文件
import React, { Component, Fragment } from "react";
import { Switch, Route, Redirect, Link } from "react-router-dom";
import { hot } from "react-hot-loader";
import View1 from "CMT/view1";
import View2 from "CMT/view2";
import "STYLE/style.scss";
class Main extends Component {
constructor(props, context) {
super(props, context);
this.state = {
title: "Hello World!"
};
}
render() {
return (
<Fragment>
<p>{this.state.title}</p>
<Link to="/view1/">View1</Link>
<Link to="/view2/">View2</Link>
<Switch>
<Route exact path="/" component={View1} />
<Route path="/view1/" component={View1} />
<Route path="/view2/" component={View2} />
<Redirect to="/" />
</Switch>
</Fragment>
);
}
}
export default hot(module)(Main);
然后重新执行命令测试即可
HTMl&CSS热更新
如果足够认真的话你们会发现现在如果修改样式之后代码会更新,但是浏览器不会自动刷新了.
那是因为热更新的代码是输出在内存中,而我们之前引入了mini-css-extract-plugin
插件提取css单独合并一个模块之后,尽管代码也会更新,但是html引用的link
没有改变所以沿用的还是更新前的样式,html更新暂时没找到方法,但是不影响React开发,而css更新我们可以用过环境配置,不提取样式解决.
当然,如果真的需要实现html更新的话,可以简单粗暴的换回全局刷新即可
hot: true,
// hotOnly: true
rules.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = [{
test: /\.(js|jsx)$/, // 匹配文件
exclude: /node_modules/, // 过滤文件夹
use: {
loader: "babel-loader"
}
}, {
test: /\.s?css$/, // 匹配文件
use: [process.env.NODE_ENV !== "SERVER" ? {
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
publicPath: '../'
}
} : 'style-loader', // 使用<style>将css-loader内部样式注入到我们的HTML页面,
'css-loader', // 加载.css文件将其转换为JS模块
'sass-loader' // 加载 SASS / SCSS 文件并将其编译为 CSS
]
},
{
test: /\.(html)$/,
use: {
loader: "html-loader",
options: {
attrs: ["img:src", "img:data-src", "audio:src"],
minimize: true
}
}
},
{
test: /\.(png|svg|jpe?g|gif)$/i, // 图片处理
use:
process.env.NODE_ENV === "PROD"
? [
{
loader: "url-loader",
options: {
name: "[name].[hash:5].[ext]",
limit: 20 * 1024, // size <= 50kb
outputPath: "img"
}
},
{
loader: "image-webpack-loader",
options: {
// Compress JPEG images
mozjpeg: {
progressive: true,
quality: 65
},
// Compress PNG images
optipng: {
enabled: false
},
// Compress PNG images
pngquant: {
quality: "65-90",
speed: 4
},
// Compress GIF images
gifsicle: {
interlaced: false
}
}
}
]
: [
{
loader: "url-loader",
options: {
name: "[name].[hash:5].[ext]",
limit: 20 * 1024, // size <= 50kb
outputPath: "img"
}
}
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/, // 字体处理
use: ["file-loader"]
},
{
test: /\.xml$/, // 文件处理
use: ["xml-loader"]
}
]
webpack.common.js
const path = require("path"),
HtmlWebpackPlugin = require("html-webpack-plugin"),
CleanWebpackPlugin = require("clean-webpack-plugin"),
MiniCssExtractPlugin = require("mini-css-extract-plugin"),
alias = require("./alias"),
rules = require("./rules");
module.exports = {
// 入口
entry: "./src/index.js",
// 输出
output: {
// 打包文件名
filename: "[name].bundle.js",
// 输出路径
path: path.resolve(__dirname, "../dist"),
// 资源请求路径
publicPath: ""
},
module: {
rules
},
plugins: [
// 清除文件
new CleanWebpackPlugin({
dangerouslyAllowCleanPatternsOutsideProject: true,
cleanOnceBeforeBuildPatterns: ["../dist/**"],
dry: false
}),
// 提取样式文件
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename:
process.env.NODE_ENV !== "PROD"
? "[name].css"
: "style/[name].[contenthash].css",
chunkFilename:
process.env.NODE_ENV !== "PROD"
? "[id].css"
: "style/[id].[contenthash].css"
}),
new HtmlWebpackPlugin({
// title
title: "test",
// 模板
template: "index.html"
})
],
resolve: {
// 创建 import 或 require 的别名,来确保模块引入变得更简单
alias
}
};
输出名字那里也换了一下,官方推荐
For long term caching usefilename: "[contenthash].css"
. Optionally add[name]
.
默认配置如下:
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? '[name].css' : '[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
})
但是这样子即使没改动也会导致hash改变而重新打包,这里简单说一下几种常用变量的区别
- hash: 和整个项目构建相关并且全部文件公用相同hash值,即没有缓存效果只适用于开发阶段
- chunkhash: 根据入口依赖文件解析构建对应的chunk生成对应的hash值,可以保证正常业务修改不影响公共代码,因为公共代码属于一个单独模块,但是样式被打包进业务模块所以两者公用同一个chunkhash.
- contenthash: 样式模块根据自身内容而生成,做到不被业务代码改变而影响
因为对应打包路径换了一下,所以loader也需要判断一些路径
{
test: /\.s?css$/, // 匹配文件
use: [
process.env.NODE_ENV !== "SERVER"
? {
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
publicPath: process.env.NODE_ENV === "DEV" ? "./" : "../"
}
}
: "style-loader", // 使用<style>将css-loader内部样式注入到我们的HTML页面,
"css-loader", // 加载.css文件将其转换为JS模块
{
loader: "postcss-loader",
options: {
config: {
path: "./" // 写到目录即可,文件名强制要求是postcss.config.js
}
}
},
"sass-loader" // 加载 SASS / SCSS 文件并将其编译为 CSS
]
},
生产警告!!!!
你们以为这样就算完了?不,你打包生产环境看看
npm run prod
然后你会发现居然没有打包css!!??
到处查找资料发现有两种办法解决
修改引入方法
import "STYLE/style.scss"; -> require("STYLE/style.scss");
修改package.json
"sideEffects": [
"*.scss", "*.css"
]
具体原因可以看
CSS压缩
mini-css-extract-plugin
没有实现压缩功能,我们自己重新引用一个完成库optimize-css-assets-webpack-plugin
yarn add optimize-css-assets-webpack-plugin
它会在构建期间搜索css资源并且优化压缩处理.
然后生产配置文件修改webpack.prod.js
const merge = require("webpack-merge"),
common = require("./webpack.common.js"),
OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
// 原始源代码
devtool: 'source-map',
plugins: [
new OptimizeCssAssetsPlugin()
]
});
CSS增强
postcss-loader可以同通过配置增强CSS的功能,在这里我们先简单使用自动补全前缀的功能,首先
yarn add postcss-loader autoprefixer
在根目录新建postcss.config.js
作为配置文件
const autoprefixer = require('autoprefixer');
module.exports = {
plugins: [
autoprefixer({
browsers: ['iOS >= 6', 'Android >= 4', 'IE >= 9']
})
]
};
只是加载插件设定兼容的系统版本,同时也要在rules.js
修改,需要设定寻找配置的路径
{
test: /\.s?css$/, // 匹配文件
use: [process.env.NODE_ENV !== "SERVER" ? {
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
publicPath: '../'
}
} : 'style-loader', // 使用<style>将css-loader内部样式注入到我们的HTML页面,
'css-loader', // 加载.css文件将其转换为JS模块
{
loader: 'postcss-loader',
options: {
config: {
path: './' // 写到目录即可,文件名强制要求是postcss.config.js
}
}
},
'sass-loader' // 加载 SASS / SCSS 文件并将其编译为 CSS
]
}
注意引入位置
Use it aftercss-loader
andstyle-loader
, but before other preprocessor loaders like e.gsass|less|stylus-loader
, if you use any.
代理
启动服务器开发有时候需要访问外部域名请求,但是后台又没帮你解决跨域问题的话,我们可以再配置增加一个跨域配置,如下
devServer: {
// 打开模式, Iframe mode和Inline mode最后达到的效果都是一样的,都是监听文件的变化,然后再将编译后的文件推送到前端,完成页面的reload的
inline: true,
// 指定了服务器资源的根目录
contentBase: path.join(__dirname, '../dist'),
// 是否开启gzip压缩
compress: false,
port: 9000,
// 是否开启热替换功能
// hot: true,
// 是否自动打开页面,可以传入指定浏览器名字打开
open: false,
// 是否开启部分热替换功能
hotOnly: true,
proxy: {
'/api': {
// 代理地址
target: 'http://alpha.xiaohuxi.cn',
changeOrigin: true,
// 默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受
secure: true,
// 重写路径
pathRewrite: {
'^/api': ''
},
}
}
},
当下所有/api
的请求都会被转发到http://www.test.cn
地址去,更多用法参考文档http-proxy-middleware
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。