/** 如果您发现错误,请一定要告诉窝,拯救一个辣鸡(但很帅)的少年就靠您了!*/
本文基于 webpack4
什么是 webpack
webpack 是一个打包工具。
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
什么是 loader 和 plugins
loader
loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。通过 loader 可以把其他格式的文件转为 webpack 可以打包的模块。
loader 有三种配置方式
- 配置文件:通过
module.rules
字段配置。 - 内联:可以在
import
等引入语句中指定 loader。使用!
将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。 - CLI:通过 CLI 使用 loader
一组链式的 loader 将按照相反的顺序执行。
常用 loader :babel-loader、style-loader、css-loader、less-loader、postcss-loader、eslint-loader、vue-loader ……
plugins
插件目的在于解决 loader 无法实现的其他事。
常用 plugins:DefinePlugin(定义全局常量)、copy-webpack-plugin(复制文件)、extract-text-webpack-plugin(分离出CSS文件)IgnorePlugin(忽略文件)……
webpack 如何解析代码模块路径
webpack 能够解析三种文件路径
-
绝对路径
-
相对路径
在
import/require
中给定的相对路径,会添加此上下文路径(context path),以产生模块的绝对路径(absolute path)。 -
模块路径
模块将在
resolve.modules
中指定的所有目录(默认是[node_modules]
)内搜索。
如果路径指向一个文件
- 如果路径具有文件扩展名,则被直接将文件打包。
- 否则,将使用
[resolve.extensions]
选项作为文件扩展名来解析,此选项告诉解析器在解析中能够接受哪些扩展名(例如.js
,.jsx
)。
如果路径指向一个文件夹件
- 如果文件夹中包含
package.json
文件,则按照顺序查找resolve.mainFields
配置选项中指定的字段。并且package.json
中的第一个这样的字段确定文件路径。 - 如果
package.json
文件不存在或者package.json
文件中的main
字段没有返回一个有效路径,则按照顺序查找resolve.mainFiles
配置选项中指定的文件名,看是否能在import/require
目录下匹配到一个存在的文件名。 - 文件扩展名通过
resolve.extensions
选项采用类似的方法进行解析。
hash 和 chunkhash 的区别
hash
:项目中任何一个文件改动后就会被重新创建,所有的文件名都会使用相同的 hash 指纹。chunkhash
:是根据具体模块文件的内容计算所得的 hash 值,所以某个文件的改动只会影响它本身的 hash 指纹,不会影响其他文件。contenthash
:代表的是文本文件内容的 hash 值,是指打包后文件的内容。
webpack-dev-server
启动 webpack-dev-server
相当于先通过 webpack 打包,然后,启动本地静态服务器运行打包后的代码。通过设置 mode
为 development
可以使用一些 webpack 为 development
模式预设的一些配置。
可以通过 devServer
字段来配置 webpack-dev-server 。
模块热替换(HMR, hot module replacement)
模块热替换功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
- 保留在完全重新加载页面期间丢失的应用程序状态。
- 只更新变更内容,以节省宝贵的开发时间。
- 在源代码中对 CSS/JS 进行修改,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
通过设置 devServer.hot=true
可以在 webpack-dev-server 中使用 HMR 。
手写 webpack 配置
一个简单的 react 项目配置。使用了常用插件和 loader ,完整配置可见 github.com/G-lory/fron…
点击查看代码const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
// mode: 'production'
/**
* 配置入口文件
* 相当于
* entry { main: './src/index.js' }
* 可以配置多个入口
*/
entry: './src/index.js',
/**
* 配置输出路径和文件名
*/
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
module: {
rules: [
/**
* 配置 babel-loader 来使用 ES6 和 react 语法
* test 和 include/exclude 指定作用的文件
* use 指定 loader 和相关配置
*/
{
test: /\.js$/,
include: [
path.resolve(__dirname, 'src') // 指定哪些路径下的文件需要经过 babel-loader 处理
],
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
/**
* 将 eslint-loader 设置为前置 loader
* 因为 eslint 检查的是未经处理的源代码
* 要保证最先进行处理
* 通过 npx eslint --init 命令初始化一个 eslint 配置文件
*/
{
enforce: "pre",
test: /\.js$/,
include: [
path.resolve(__dirname, 'src')
],
loader: "eslint-loader"
},
/**
* v4 以后使用 mini-css-extract-plugin 代替 extract-text-webpack-plugin
* style-loader 是把样式通过 js 生成样式插入到 html
* MiniCssExtractPlugin 是提取样式到文件
* 所以使用 MiniCssExtractPlugin 就不需要 style-loader 了
* css-loader 处理 css 文件的引用 @import and url() like import/require()
* postcss-loader 配置见文件 postcss.config.js 自动添加浏览器前缀
*/
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader"
]
},
/**
* 处理图片
*/
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
},
],
}
]
},
plugins: [
/**
* 将打包好的js和css文件在指定html文件中自动引入
*/
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'public/index.html'
}),
/**
* 提取css到指定文件
*/
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
// chunkFilename: "[id].css"
}),
/**
* 打包前删除之前的生成文件
* 默认删除 <PROJECT_DIR>/dist/ 下的所有文件
*/
new CleanWebpackPlugin(),
/**
* 用于创建一些在编译时可以配置的全局常量
*/
new webpack.DefinePlugin({
VERSION: JSON.stringify('5fa3b9')
// 如果指定了 mode 会自动添加变量 process.env.NODE_ENV = mode
// "process.env.NODE_ENV": JSON.stringify("production")
}),
/**
* 复制文件 不经过webpack处理 直接复制到指定目录
* from 配置来源,to 配置目标路径
*/
new CopyWebpackPlugin([
{ from: 'src/assets/favicon.ico', to: 'favicon.ico' }
])
],
/**
* 配置代码的解析路径
*/
resolve: {
/**
* 配置路径别名
*/
alias: {
'@src': path.resolve(__dirname, 'src')
},
/**
* 搜索模块路径的目录
*/
modules: [
"node_modules"
],
// 查找路径是自动添加的文件后缀
// 减少配置以防降低查找效率
extensions: [".js", ".jsx"]
},
optimization: {
// mode 为 production 时
// 默认开启压缩
// minimize: true
// 可以通过提供一个或多个定制过的 TerserPlugin 实例,覆盖默认压缩工具(minimizer)。
minimizer: [new UglifyJsPlugin()],
},
}
复制代码
webpack 的流程、webpack 的原理、手写 plugin 和 loader
看了多篇文章之后,确定是我学不起的内容……只找到一篇相对简单的文章。
干货!撸一个webpack插件(内含tapable详解+webpack流程)
更深入的以后再学习……