webpack 是一个模块打包器,分析你的项目结构,找到 JavaScript 模块以及其它的一些浏览器不能直接运行的拓展语言(sass,less,typeScript等),并将其打包为合适的格式以供浏览器使用。
基本概念
entry, 工程的入口文件配置
1、字符串
entry: './src/index.js',
2、数组类型:拼接多个文件到一个文件
entry: ['./src/index.js', './src/main.js'],
3、对象类型:分别打包
entry: {
index: './src/index.js',
main: './src/main.js'
}
output, 打包的输出的文件配置
path 告诉 Webpack 打包的结果存储在哪里。
publicPath 用于在生产模式下更新内嵌到 css、html 文件里的 url 值。
loader
loader 用于处理各种不同类型模块的处理器,将这些类型的模块处理为浏览器可运行和识别的代码,从而使 webpack 具有了强大而灵活的能力。
loader 本质上是一个函数,输入参数是一个字符串,输出参数也是一个字符串。webpack 会按照从右到左的顺序执行 loader。
-
babel-loader 将 es6 以上代码转换为 es5 代码;
-
style-loader:将 css 模块以 style 标签的形式加入到 html 中
-
css-loader:主要用来解析 css 中的静态资源,像import/require引入的方式一样去解释@import/url()
-
postcss-loader:可以对 css 进行各种处理,配合 autoprefixer 自动添加 css 前缀
-
autoprefixer:自动补全css前缀
-
sass-loader/less-loader:将sass/less代码转换为css
-
url-loader 和 file-loader 是一对用来处理图片、svg、视频、字体等静态资源文件的 loader
url-loader 可以设置图片大小限制,当图片超过限制时,其表现行为等同于 file-loader,而当图片不超过限制时,则会将图片以 base64 的形式打包进 css 文件,以减少请求次数
plugins, 插件,在webpack打包过程中不同时机执行一些任务,比如清除打包目录、复制静态文件、抽取css文件
1、HtmlWebpackPlugin 插件可以用来生成包含你所有打包文件(js和css)的html文件,特别是你在打包文件名配置了hash,就不得不用这个插件了。
new HtmlWebpackPlugin({
template: './index.html', // 模板文件
filename: 'index.html'
})
template: HtmlWebpackPlugin生成html文件的模板,如果简单的话可以直接通过其他配置项生成,不必单独提供一个html文件模板,详情可参考HtmlWebpackPlugin
filename: 输出的html文件名,规则和output的filename相同
inject: 是否注入打包的文件,包括js和通过MiniCssExtractPlugin打包输出的css文件,默认为true
minify: 是否压缩
2、HotModuleReplacementPlugin 正常情况下,hmr只会更新模块,不会触发页面刷新
3、MiniCssExtractPlugin 将一个 chunk 中的 css 抽取为一个单独的 css 文件,如果 chunk 中不包含css,则不生成文件。
new MiniCssExtractPlugin({
filename: "[name].[chunkhash:8].css",
chunkFilename: "[id].css"
}),
4、CopyWebpackPlugin 用来处理静态文件,可以将文件或者文件夹原封不动地移动到打包目录。
new CopyWebpackPlugin([{
from: path.join(__dirname, './favicon.ico'),
to: path.join(__dirname, 'prod')
}])
5、CleanWebpackPlugin 用来清除打包目录,主要用于每次重新生成的时候,清除残留文件。在文件有hash值的情况下,是必要的。
new CleanWebpackPlugin('prod/*.*', {
root: __dirname,
verbose: true,
dry: false
})
6、splitChunks:代码拆分
chunks(默认是async):initial、async和all
minSize(默认是30000):形成一个新代码块最小的体积
minChunks(默认是1):被多少模块共享
maxAsyncRequests(默认是5):按需加载时候最大的并行请求数。
maxInitialRequests(默认是3):一个入口最大的并行请求数
test: 用于控制哪些模块被这个缓存组匹配到 RegExp、String和Function
name:打包的chunks的名字
priority :缓存组打包的先后优先级。
splitChunks: {
chunks: 'all',
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '-',
name: true,
cacheGroups: {
react: {
test: /react/,
name: "react",
priority: 1,
minChunks: 1,
}
}
}
7、runtimeChunk:runtime,用于管理被分出来的包
Mode
区分开发环境和生成环境, webpack会根据 mode 值自动帮你做一个不同的优化
1、production(默认值)
在DefinePlugin中将process.env.NODE_ENV设置为production
默认启用了如下插件:
- FlagDependencyUsagePlugin:标记没有用到的依赖,
- FlagIncludedChunksPlugin:给当前chunk包含的chunkid加入chunk名之中 ,
- ModuleConcatenationPlugin:作用域提升,
- NoEmitOnErrorsPlugin:在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段,
- OccurrenceOrderPlugin:排序输出,通过模块调用次数给模块分配ids,常用的ids就会分配更短的id,使ids可预测,减小文件大小,
- SideEffectsFlagPlugin:标记不包括副作用的模块,优化打包,
- UglifyJsPlugin:用来对js文件进行压缩,从而减小js文件的大小,加速load速度
2、development
在DefinePlugin中将process.env.NODE_ENV设置为development
默认启用了如下插件:
- NamedChunksPlugin:自定义chunks的名字,
- NamedModulesPlugin:自定义模块的名字
3、mode的两种使用方式
配置: module.exports = { mode: 'production'}
命令行: webpack --mode=production
4、打包结果对比
production
2、development
实践
打包命令
Webpack 作为模块打包工具,提供两种用户交互接口:
-
Webpack CLI tool:默认的交互方式(已随Webpack本身安装到本地) 有利于生产模式下打包,
-
webpack-dev-server:一个Node.js服务器(需要从npm自行安装) 基于Express.js框架开发的web server,默认监听8080端口。
可以通过以下两种方式向 webpack-dev-server 传入参数:
1、通过 CLI 传参
webpack-dev-server --hot --inline
2、通过webpack.config.js文件的"devServer"对象
devServer: {
inline: true, // 刷新浏览器
hot:true, // 重新加载改变的部分,HRM失败则刷新页面
proxy: { // 设置代理
'/api/file/*': {
target: 'https://www.google.com/',
changeOrigin: true, // 文件上传需要设置为 true
},
}
}
第一次打包 commonjs 模块化的处理 demo (init)
打包结果 webpack-test/dist/dist.js
总结
原来 webpack 就是把我们写的代码用一个包装函数包装了起来, 在执行__webpack_require__的时候调用一下包装函数,通过包装函数内部的代码重写了参数中参数 module 的 exports 属性, 获取到我们编写的模块的主体代码
由于 index.js 中有 require('./foo') 所以 index.js 生成的包装函数参数中多了__webpack_require__用于导入 foo 模块
es6 Module 模块化的处理 demo (es6 Module)
打包结果
总结
module.exports 变成了__webpack_exports__
使用 es6 模块导入语法(import)的地方, 给__webpack_exports__添加了属性__esModule
commonjs es6 module 混合 demo (混合)
打包结果
打包 html
html-loader html-webpack-plugin
代码
const path = require('path');
module.exports = {
mode: 'development',
// mode: 'production',
// 这里是打包的入口文件(相对路径)
entry: './index.js',
output: {
// 打包结果存放的位置(必须用绝对路劲)
path: path.resolve(__dirname, 'dist'),
// path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
// 打包结果文件名称
filename: 'bundle.js',
},
};
打包 css
css-loader mini-css-extract-plugin
可以将 use 配置为一个数组,loader 从右往左依次执行,且前一个 loader 的结果是下一个 loader 的输入。最后一个 loader 的输出就是我们最终要的结果。 一个 sass 文件首先经过 sass-loader 处理,变成 css 文件,又经过 postcss-loader 处理,添加浏览器前缀等功能,接着交给 css-loader 去解析 css 文件引用的静态变量,最后由 style-loader 以 script 标签的形式加入到 html 中。
代码
const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const Webpack = require('webpack')
module.exports = {
mode: 'development',
// mode: 'production',
// 这里是打包的入口文件(相对路径)
entry: './src/index.js',
output: {
// 打包结果存放的位置(必须用绝对路劲)
path: path.resolve(__dirname, 'dist'),
// path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
// 打包结果文件名称
filename: 'index.[hash:8].js',
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[chunkhash:8].css",
chunkFilename: "[id].css"
}),
new Webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: './index.html', // 模板文件
filename: 'index.html'
})
]
}
打包 less
less-loader postcss-loader autoprefixer
代码
const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const Webpack = require('webpack')
module.exports = {
mode: 'development',
// mode: 'production',
// 这里是打包的入口文件(相对路径)
entry: './src/index.js',
output: {
// 打包结果存放的位置(必须用绝对路劲)
path: path.resolve(__dirname, 'img'),
// path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
// 打包结果文件名称
filename: 'index.[hash:8].js',
},
module: {
rules: [
{
test:/\.css/,
use:[MiniCssExtractPlugin.loader,"css-loader",{
loader: "postcss-loader",
options: {
plugins: () => [require('autoprefixer')]
}
}]
},
{
test:/\.less$/,
use:[MiniCssExtractPlugin.loader,"css-loader",{
loader: "postcss-loader",
options: {
plugins: () => [require('autoprefixer')]
}
},"less-loader"]
},
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'img:data-src', 'audio:src'],
minimize: true
}
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[chunkhash:8].css",
chunkFilename: "[id].css"
}),
new Webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: './index.html', // 模板文件
filename: 'index.html'
})
]
}
打包 img
url-loader file-loader
代码
const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const Webpack = require('webpack')
module.exports = {
mode: 'development',
// mode: 'production',
// 这里是打包的入口文件(相对路径)
entry: './src/index.js',
output: {
// 打包结果存放的位置(必须用绝对路劲)
path: path.resolve(__dirname, 'img'),
// path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
// 打包结果文件名称
filename: 'index.[hash:8].js',
},
module: {
rules: [
{
test:/\.css/,
use:[MiniCssExtractPlugin.loader,"css-loader",{
loader: "postcss-loader",
options: {
plugins: () => [require('autoprefixer')]
}
}]
},
{
test:/\.less$/,
use:[MiniCssExtractPlugin.loader,"css-loader",{
loader: "postcss-loader",
options: {
plugins: () => [require('autoprefixer')]
}
},"less-loader"]
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
publicPath: "./images/",
outputPath: "images/"
}
}]
},
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'img:data-src', 'audio:src'],
minimize: true
}
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[chunkhash:8].css",
chunkFilename: "[id].css"
}),
new Webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: './index.html', // 模板文件
filename: 'index.html'
})
]
}
webpack 实现代码拆分 splitChunk
代码
const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin=require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const Webpack = require('webpack')
module.exports = {
// mode: 'development',
// mode: 'production',
// 这里是打包的入口文件(相对路径)
entry: './src/index.js',
output: {
// 打包结果存放的位置(必须用绝对路劲)
path: path.resolve(__dirname, 'prod'),
publicPath: 'http://localhost:8000/',
// path: path.resolve('/Users/rainzhao/collect/webpack/demo/1', 'dist'),
// 打包结果文件名称
filename: 'index.[hash:8].js',
},
module: {
rules: [
{
test: /src\/\.js$/,
use: 'babel-loader',
},
{
test:/\.css/,
use:[MiniCssExtractPlugin.loader,"css-loader",{
loader: "postcss-loader",
options: {
plugins: () => [require('autoprefixer')]
}
}]
},
{
test:/\.less$/,
use:[MiniCssExtractPlugin.loader,"css-loader",{
loader: "postcss-loader",
options: {
plugins: () => [require('autoprefixer')]
}
},"less-loader"]
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 1,
name: '[name].[hash:8].[ext]',
outputPath: "images/"
}
}]
},
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'img:data-src', 'audio:src'],
minimize: true
}
}
}
]
},
optimization: {
splitChunks: {
chunks: 'all',
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '-',
name: true,
cacheGroups: {
react: {
test: /react/,
name: "react",
priority: 1,
minChunks: 1,
}
}
},
runtimeChunk: {
name: "manifest"
}
},
plugins: [
new CleanWebpackPlugin('prod/*.*', {
root: __dirname,
verbose: true,
dry: false
}),
new CopyWebpackPlugin([{
from: path.join(__dirname, './favicon.ico'),
to: path.join(__dirname, 'prod')
}]),
new MiniCssExtractPlugin({
filename: "[name].[chunkhash:8].css",
chunkFilename: "[id].css"
}),
new Webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: './index.html', // 模板文件
filename: 'index.html'
})
]
}
按需加载
// index.js
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
// 点击按钮加载 foo
import(/* webpackChunkName: "foo" */ './foo').then(function(module){
const foo = module.default;
console.log(foo);
})
}
总结
webpack 把你的项目当做一个整体,通过一个给定的入口文件(如:index.js),从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个浏览器可识别的JavaScript文件
使 es6 中不能直接使用的特性,转换了es5中浏览器支持的方法。
sass,less 等 预处理器
模块化,让我们可以把复杂的应用细化为小的组件;