webpack 是什么
webpack 是一种前端资源构建工具,一个静态打包器
1,前端构建工具:前端资源就是浏览器不认识的web资源,比如 less,sass,ts,包扩 js 的高级语法。这些文件要能够在浏览器里正常工作,必须经过编译处理,webpack 就是可以集成这些编译工具的一个总的构建工具。
2,静态打包器:静态模块就是开发中的各种资源文件,webpack 根据依赖关系生成一个依赖关系图,这个关系图就包含着应用程序所需要的每个模块,然后将所有这些模块打包成少量的 bundle(通常只有一个),由浏览器加载。
为什么需要webpack
这个问题可以和没有使用构建工具对比一下,看下列出来的几项不使用构建工具的痛点:
- web开发时调用后端接口时跨域,需要其他代理工具来规避 (webpack 可以设置代理解决跨域)
- 代码改动后手动刷新浏览器,如果有缓存必须先清除缓存刷新比较麻烦 (webpack 启动时 node 服务会及时更新)
- 像 js 和css 很多新语法有兼容问题不能使用使开发受影响 (webpack 打包时会考虑兼容问题)
- 打包问题,需要额外的打包平台打包,自己编写打包脚本,对图片解压,打包 js,css 都要一一处理。(webpack 打包)
像这些问题 webpack 都做了处理,我们只要做些简单的配置就可以上手使用。
webpack 使用
webpack 核心配置
entry (入口)
webpack 以哪个文件作为入口起点开始打包,分析构建其内部依赖图。进入入口后 webpack 会找出有哪些模块和库是入口依赖的(直接或者间接)
webpack.config.js
// 用法
// string方式 这种形式为单入口,打包形成一个 chunck,chunck 的名称默认是 main.js。输出一个 bundle
const config = {
entry: './src/index.js'
}
const config = {
entry:{
main: './src/index.js'
}
}
// object方式 多入口, 有几个入口文件就形成几个 chunck,输出几个bundle。此时 chunck名称就是对象 key 值
const config = {
entry:{
app: './src/index.js',
point: './src/point.js'
}
}
//array方式 多入口,所有的入口只生成一个 chunck,输出一个 bundle文件。
const config = {
entry: ['./src/index.js', './src/point.js']
}
output (输出)
webpack 打包后的资源bundles 输出到哪里以及如何命名。(基本上,整个应用程序解构都会被编译到指定的输出路径的文件夹中)
Webpack.config.js
//如果只有一个输出文件可以写成固定的静态不变的名称 fileName
const config = {
output:{
fileName: "bundle.js",
path: '/'
}
}
//有多个chunck 要输出时可以使用变量。
//
const config = {
entry:{
app: './src/index.js',
point: './src/point.js'
}
output:{
//输出文件的文件名,[name]代表用内置的 name 来替换,可以看成一个模块字符串函数,每个生成的 chunck 都通过这个函数拼接出文件名称且是唯一的。
fileName: "[name].js",
//输出文件存放在本地的目录,必须是 string 类型的绝对路径。 路径为 './dist/app.js', './dist/point.js'
path: path.resolve(_dirname, 'dist'),
//所有资源引入公共路径前缀,是 string 类型,默认为空字符'',即使用相对路径.(一般用于生产环境,谨慎使用)
publicPath: ''
}
}
publicPath 举个栗子:把构建出来的资源文件加载到 cdn 服务上,利于加快页面打开速度。
publicPath: 'https://cdn.example.com/assects/'
这时发布到线上的 html在引入js 时就需要
<script src='https://cdn.example.com/assects/a_12345.js'></script>
loader
webpack 自身只能理解 js 和 json 文件,loader让 webpack 能够处理其他文件。
loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader允许直接在 JavaScript 模块中 import
CSS文件
-
loader 有三种方式
配置:在webpack.config.json 中指定 loader (推荐)
内联: 每个import 语句中显示指定
CLI: 在命令中指定
Webpack.config.js
module.exports = {
module: {
//loader执行顺续,由上至下有有至左
rules: [
//打包样式文件,打包过程中只要打包到后缀为 css 文件都会使用css-loader和 style-loader去加载这个文件
{
// 正则匹配所有.css后缀的样式文件
test: /\.css$/,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
{
//postcss 用 js工具 和插件转换 css 的工具。作用为 css 添加前缀,比如-webkit。编译使大多数浏览器都能认识,css-modules 解决全局命名冲突问题。css 兼容处理 postcss,注意需要在package.json配置browserslist
loader: 'postcss-loader',
//相关配置参数
options: {
postcssOptions: {
//依赖插件 帮postcss找到package.json中的browserslist配置,根据配置加载指定的兼容性样式.
plugins: [require("postcss-preset-env")()]
}
}
},
//将 sass 转换为 css
'sass-loader'
]
},
//
{
test: /\.js$/,
//排除node_modules文件不要进行babel-loader
exclude: /node_modules/,
//只是用一个 loader 时推荐写法
loader: 'babel-loader',
options:{
presets:[
[
// 预设:指示babel做怎么样的兼容处理 babel-preset-env支持es6,7,8的语法
"@babel/preset-env",
{
//按需加载
useBuiltIns: "usage",
// 一些浏览器不能正常解析es6 语法时,corejs帮我们自动进行兼容性处理
corejs: {
//版本
version: "3",
},
//需要兼容的浏览器
targets: "defaults",
}
]
]
}
},
{
test: /\.(png|jpg|jpeg)$/,
use: [
{
//将小于limit的图片转成base64来减少http请求,如果大于就用http请求
loader: "url-loader",
options: {
name: "[name]_[hash:8].[ext]",
// 单位为(b) 10240 => 10kb
// 如果小于10kb则转换为base64打包进js文件,如果大于10kb则打包到dist目录
limit: 10240,
}
}
]
},
]
}
}
样式:style-loader、css-loader、less-loader、sass-loader等
文件:raw-loader、file-loader 、url-loader等
编译:babel-loader、coffee-loader 、ts-loader等
校验测试:mocha-loader、jshint-loader 、eslint-loader等
plugin (插件)
扩展 webpack 的能力,包括:打包优化,资源管理,注入环境变量。
// CleanWebpackPlugin帮助你在打包时自动清除dist文件,学习时使用比较方便
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); //从webpack5开始,webpack内置了该功能,只要在ouput中配置clear为true即可
// HtmlWebpackPlugin创建html文件,并自动引入打包输出的bundles文件。支持html压缩。
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 将CSS提取到单独的文件中。它会为每个chunk创造一个css文件。需配合loader一起使用
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 在Webpack构建过程中搜索CSS资源,并优化\最小化CSS
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
entry: 'index.js',
output: {
path: __dirname + '/dist',
filename: 'index_bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
// MiniCssExtractPlugin.loader的作用就是把css-loader处理好的样式资源(js文件内),单独提取出来 成为css样式文件
MiniCssExtractPlugin.loader,//生产环境下使用,开发环境还是推荐使用style-loader
"css-loader",
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
//可配置参数,可参考:https://segmentfault.com/a/1190000013883242
title:"my app", //网页 document.title 的配置
filename: 'assets/admin.html', // 在output.path 目录下生成 assets/admin.html 文件。默认 index.html
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: "css/index_build.css",
}),
new OptimizeCssAssetsWebpackPlugin(),
]
}
mode (模式)
Webpack 使用相应模式配置。mode分为development/production 两种模式,默认为 peoduction
module.exports = {
mode: 'development'
}
官网解释:
选项 | 描述 |
---|---|
development | 会将 definePlugin中的 process.env.NODE_ENV 设置成 development,为模块和 chunck 启用有效名称 |
production | 会将 definePlugin中的 process.env.NODE_ENV 设置成production,启用FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。 |
-
DefinePlugin:定义全局变量process.env.NODE_ENV,区分程序运行状态。
-
FlagDependencyUsagePlugin:标记没有用到的依赖。
-
FlagIncludedChunksPlugin:标记chunks,防止chunks多次加载。
-
ModuleConcatenationPlugin:作用域提升(scope hosting),预编译功能,提升或者预编译所有模块到一个闭包中,提升代码在浏览器中的执行速度。
-
NoEmitOnErrorsPlugin:防止程序报错,就算有错误也继续编译。
-
TerserPlugin:压缩js代码。
其他常用配置
module.exports = {
// 解析模块的规则:
resolve: {
// 配置 给文件路径取别名。
alias: {
//不论哪个文件用到 store 只需要 import Store from 'Stroe'就可以了
"Store": path.resolve(__dirname, "../src/store")
},
// 配置 引入文件时省略文件路径的后缀名。默认省略js和json也是webpack默认认识的两种文件类型。
extensions: [".js", ".json", ".css","jsx"], // 新加css,jsx文件
// 告诉webpack解析模块是去找哪个目录,默认是 ["node_modules"],如果没有就去上一层
// 该配置明确告诉webpack,直接去上一层找node_modules。
modules: [path.resolve(__dirname, "../node_modules")],
},
// devServer(开发环境下配置):
//参考文档:https://blog.csdn.net/lin_fightin/article/details/115447016
devServer: {
// 指定被访问html页面所在的目录
contentBase: path.resolve(__dirname, "build"),
// 为每个静态文件开启gzip压缩
compress: true,
host: "localhost",
port: 5000,
open: true, // 自动打开浏览器
hot: true, //开启HMR功能
// 设置代理
proxy: {
// 一旦devServer(5000端口)接收到/api/xxx的请求,就会用devServer起的服务把请求转发到另外一个服务器(3000)
// 以此来解决开发中的跨域问题
api: {
target: "htttp://localhost:3000", //映射的目标(代理成功域名)
// 发送请求时,请求路径重写:将/api/xxx --> /xxx (去掉/api)
pathRewrite: {
"^api": "",
},
},
},
},
// optimization(生产环境下优化配置)
optimization: {
splitChunks: {
// 提取公共代码 推荐(同步和异步都)
chunks: "all",
//引入的库必须大于30kb,才做代码分割(默认)
minSize: 30000,
},
minimizer: [
// 配置生产环境的压缩方案:js和css
new TerserWebpackPlugin({
// 多进程打包并发运行,提高构建速度。默认 true ?????
parallel: true,
terserOptions: {
// 启动source-map
sourceMap: true,
},
}),
],
},
};
webpack打包优化
开发环境优化
1, 使用 source-map
为了更容易追踪错误和警告,webpack 提供了 source-map 功能,一种将构建后的代码映射回源代码。如果错误来自于某个文件,source-map 就会告诉我们。
配置开启 source-map 功能
devtool:'source-map',
source-map 各选项常用组成:[inline-|eval-][cheap-[module-]]source-map
参考:zhuanlan.zhihu.com/p/67858986
2, HMR(模块热替换 hot moudule replacement)
运行时更新各种模块不需要完全刷新,只更新变更内容,调整样式迅速,避免了大部分的网络请求、浏览器重新渲染、app解析编译显示。
配置实现
devserver:{
hot:true,
},
plugin:[
new webpack.HotModuleReplacementPlugin()
]
开启 HMR 后还需要一些配置
样式文件
:style-loader 内部实现,所以只要loader中配置了style-loade就可直接使用HMR功能
js文件
:需要修改源代码,接收更新通知
/***
** 启用 webpack 内置的 HMR插件后, module.hot 接口就会暴露在 index.js 中,
** 接下来需要在 index.js 中配置告诉 webpack 接受HMR的模块( print.js ):
*/
if(module.hot){
module.hot.accept('./print.js', function(){
console.log('hot module replacement')
printMe()
})
}
参考:zhuanlan.zhihu.com/p/30669007
生产环境优化
1,oneOf
默认情况下,文件会匹配rules 下的每个规则,即使匹配到了也会继续向下匹配。使用 oneOf 时一旦匹配到则不会再向下执行
//如下在 oneOf中只有第一个 loader 会生效,@babel-loader没作用。
rules:[
{
oneOf:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader",
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "@babel-loader",
}
]
}
]
//如果一个文件需要多个loader 处理可以放在外面
//一
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader",
},
{
oneOf:[
//只会匹配成功一次,所以如果有一种类型的文件需要使用多个loader,要么使用use数组,要么放到oneOf之外
{
test: /\.js$/,
exclude: /node_modules/,
loader: "@babel-loader",
}
]
}
]
//二
rules:[
{
oneOf:[
//只会匹配成功一次,所以如果有一种类型的文件需要使用多个loader,要么使用use数组,要么放到oneOf之外
{
test: /\.js$/,
exclude: /node_modules/,
use:[
{loader: "eslint-loader",}
]
},
{
test: /\.js$/,
exclude: /node_modules/,
use:[
{loader: "@babel-loader",}
]
}
]
}
]
2,缓存
在编译打包时可以对文件做缓存,缓存有两种方式:
1,解析文件 loader 自身待的缓存 (babel-loader)
2,专门的loader (cache-loader)
开启缓存后在打包编译时对于未更改的文件可以读取缓存,不再重新编译提高打包速度。
{
test: /\.js$/,
use: [
//使用cache-loader,放在babel-loader前可对babel编译后的js文件做缓存。
"cache-loader",
{
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",// 预设:指示babel做怎么样的兼容处理
]
],
// 开启babel缓存,第二次构建时,会读取之前的缓存。
cacheDirectory: true,
}
}
]
}
######3, 多进程打包(thread-loader)
一般只有在打包编译花费时间较长时才会用 thred-loader,因为这个loader 启动和通信都是有开销的~
// "thread-loader"放在babel-loader前,就会在babel-loader工作时进行多进程工作。 ??????
{
loader: "thread-loader",
options: {
workers: 2, // 启动进程个数,默认是电脑cpu核数-1
},
},
{
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
],
],
},
}
######4, 外部扩展(externals)
告诉 webpack 要构建的代码中使用了哪些不用被打包的模块,这些模块可以使用外部引入(cdn)
module.export = {
externals: {
// 把导入语句里的 jquery 替换成运行环境里的全局变量 jQuery
jquery: 'jQuery'
}
}
//源代码
import $ from 'jquery';
//配置了 externals 即使代码中引入了这个文件库,webpack 也不会打包到 bundle 中,而是使用全局变量
5, dll
6, 树摇
7,代码分割
[专栏原文]:(juejin.cn/post/697174…)