webpack 简介
webpack是一个静态模块打包器,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
这里将的webpack的版本是: wepack:4.41.0
安装
yarn add webpack webpack-cli --dev
命令使用 webpack --config config文件路径 --mode=development(production)
一般加入在package.jons的script,使用yarn build命令
"scripts": {
"build": "webpack --config webpack.config.js --mode=development"
},
webpack 核心概念
entry 入口
entry是创建依赖关系图的起点,起点可以是单个文件,也可以是多个文件
- 单个文件
module.exports = {
entry: "src/index.js"
}
或者
module.exports = {
entry: {
main: "./src/index.js"
}
}
- 多个文件
module.exports = {
entry: {
app: "./src/app.js",
vendor: "./src/vendor.js"
}
}
出口 output
output告诉webpack在哪里输出文件
- 基本语法
module.exports = {
output: {
filename: "文件名",
path: "最后文件输出的路径"
}
}
- 单个入口的output
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname,"dist")
}
}
- 多个入口的output
module.exports = {
entry: {
app: "./src/app.js",
vendor: "./src/vendor.js"
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist")
}
}
loader
loader将那些非javascript文件转化为webpack能处理的模块
基本语法
- 在配置文件中指出
module.exports = {
module: [
{
test : /\.css$/, // 正则指出需要转化的那些文件
use: ["style-loader","css-loader"] // 指出转化用到的loader
// use 也可指定option
use: {
loader: 'css-loader',
options: {
modules: true // 这里为loader的参数
}
}
}
]
}
- 内联
import Styles from 'style-loader!css-loader?modules!./styles.css';
- cli
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
常用的loader
style-loader 与 css-loader (官网建议一起用)
用于将css插入dom中, 需要先yarn add style-loader css-loader --dev,
配置文件语法如下
module.exports = {
module: [
rules: [
test: /\.css$/,
use: ["style-loader","css-loader"]]
]
}
在引入css的时候,用import/require() ,会在最终生成的js里面动态插入到head 到style标签中,如下图:
这里需要注意一点:(1)loader 是从右到做执行到,webpack在打包时遇到import/require到css文件会先利用css-loader去解析css文件,然后再利用style-loader把解析生成到css动态插入到head的script标签里面。(因此style-loader和css-loader是有顺序的)
less-loader
解析less语法为css语法,首先需要安装yarn add less less-loader style-loader css-loader
,配合一起使用,来解析css文件,配置文件语法如下:
module.exports = {
...
module: {
rules: [{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
}]
}
};
结果如下:
less 文件为
最后打包运行后的less 被插入到了style标签中
sass-loader
sass-loader是把sass语法编译为css ,首先需要安装 yarn add sass-loader node-sass style-loader css-loader --dev
module.exports = {
...
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader" // 将 JS 字符串生成为 style 节点
}, {
loader: "css-loader" // 将 CSS 转化成 CommonJS 模块
}, {
loader: "sass-loader" // 将 Sass 编译成 CSS
}]
}]
}
};
其中会有node-sass 安装出现问题的情况,需要在.npmrc指定镜像
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
file-loader
import require的图片引入到模块中
配置
module.exports = {
...
module: {
rules: [
{
test: /.(png|svg|jpg|git)$/,
use: [
{
loader: "file-loader",
options: {
outputPath: "images/" // 最后输出到图片到相对位置
}
}
]
}
]
}
};
最后图片被打包到 /dist/images目录中
也可用于加载字体
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
csv-loader xml-loader
加载数据
module.exports = {
....
module: {
rules: [
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
{
test: /\.xml$/,
use: [
'xml-loader'
]
}
]
}
};
mode 模式
值有development 或 production,告诉webpack启用那个模式下的优化配置
module.exports = {
mode: 'production'
};
pluign 插件
本质
webpack 插件是一个具有 apply 属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。
例如下面就是个简单的pluign例子
const pluginName = 'ConsoleLogOnBuildWebpackPlugin'; // 应该是驼峰式命名的插件名称
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, compilation => {
console.log("webpack 构建过程开始!");
});
}
}
用法
module.exports = {
...
plugins: [new ConsoleLogOnBuildWebpackPlugin()]
}
常用插件
HtmlWebpackPlugin
- 安装
yarn add html-webpack-plugin --dev
- 作用 自动生成一个html文件,并把打包好的需要的script都加进去
- 语法
var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');
var webpackConfig = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin({
title: "hello world"
})]
};
- 参数
- title html 的title
- filename 生成的文件名 默认是index.html
- favicon 图标
- template index.html 路径
CleanWebpackPlugin
- 安装 yarn add clean-webpack-plugin --dev
- 使用
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
plugins: [
new CleanWebpackPlugin(), // 每次打包前清空dist文件加
new HtmlWebpackPlugin({
title: 'Output Management'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
模块
webpack 支持的模块
- ES2015 import 语句
- CommonJS require() 语句
- AMD define 和 require 语句
- css/sass/less 文件中的 @import 语句。
- 样式(url(…))或 HTML 文件(<
img src=...>
)中的图片链接(image url)
模块解析
使用enhanced-resolve解析路径,支持3中解析规则方式
- 绝对路径
- 相对路径
- 模块路径
其它配置
devtool
此选项控制是否生成,以及如何生成 source map。
devtool: “inline-source-map” 只在开发环境中用于排查错误在源码中
webpack --watch
如果其中一个文件被更新,代码将被重新编译,所以你不必手动运行整个构建。
webpack-dev-server
起一个web服务器而且实时加载
- 安装 yarn add webpack-dev-server --dev
- 在插件中配置
module.exports = {
...
devServer: {
contentBase: "./dist"
}
}
- 在package.json 加入
{
"script" : {
"start": "webpack-dev-server --open",
}
}
- 属于命令yarn start 会打开一个localhost:8080页面
HRM 热模块替换
- 开启
{
devServer: {
hot: true
}
}
- 使用
import print from "./print";
function component() {
const element = document.createElement("div");
const btn = document.createElement("button");
element.innerHTML = "hello word, threejs";
btn.innerHTML = "click me to print";
btn.onclick = print;
element.appendChild(btn);
return element;
}
let element = component(); // 当 print.js 改变导致页面重新渲染时,重新获取渲染的元素
document.body.appendChild(element);
if (module.hot) {
module.hot.accept("./print.js", () => {
console.warn("Acceptiong th updated printMe module");
document.body.removeChild(element);
element = component(); // 重新渲染页面后,component 更新 click 事件处理
document.body.appendChild(element);
});
}
tree shaking
通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)
- 配置
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'development',
optimization: {
usedExports: true,
},
};
build后会看到,
没有用到的方法squre被标示出来了,但是没有删除掉
2. 删除无用的代码
在上面的配置 加上minimize: true
optimization: {
usedExports: true,
minimize: true
},
可以看到square以及被移除
3. 在生产环境中 ,只需要指定mode:"production"就会自动tree shaking 同时缩小文件体积
开发环境配置
开发环境和生成环境的性能要求区别:
(1)开发环境:要求实时刷新和source map
(2) 生产环境: 要求更小的bundle 以及更优化的资源,以改善加载时间
因此开发环境和生产环境的配置需要分开
- 将两个环境公用动配置放在webpack/common.webapck.js 文件中,代码如下:
主要是定义入口出口和loader 以及plugin
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
app: "./src/index.js"
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./index.html",
favicon: "./favicon.ico"
})
],
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"]
},
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"]
},
{
test: /\.(png|jpg|svg|gif)$/,
use: [
{
loader: "file-loader",
options: {
outputPath: "images/"
}
}
]
}
]
}
};
- 开发环境的配置,需要用到webpack-merge去merge 两者的配置,webpack/webpack.dev.js的代码如下
除了common中有的,加入了tree shaking 和 source-map
const merge = require("webpack-merge");
const common = require("./webpack.common");
const ConsoleLogOnBuildWebpackPlugin = require("../src/example.plugin");
module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
devServer: {
contentBase: "../dist",
hot: true
},
optimization: {
usedExports: true,
minimize: true
},
plugins: [new ConsoleLogOnBuildWebpackPlugin()]
});
- 生产环境的配置,同样需要webpack-merge,代码如下:
mode: “production”, 就自带了tree shaking和代码压缩 , 同时把出口文件加上hash,每次生成不一样,放置缓存
const path = require("path");
const merge = require("webpack-merge");
const common = require("./webpack.common");
module.exports = merge(common, {
mode: "production",
output: {
filename: "[name].[hash].bundle.js",
path: path.resolve(__dirname, "../dist")
},
});
- 最后在package.json 的script 指令中加入指定不同的文件
"scripts": {
"start": "webpack-dev-server --open --config webpack/webpack.dev.js",
"build": "webpack --config webpack/webpack.prod.js"
}
代码分离
代码分离是指能够把不同的模块打包到不同的bundle中,起到减小文件体积,同时能够控制资源加载优先级,起到优化加载的作用。
有3种代码分离的方法,第一种上面讲过entry 可以定义不同文件,第二种方法是利用插件plitChunksPlugin 把重复的模块提取到单独的文件,放置重复在不同页面使用。第三种是动态加载模块,没用过这里就不讲了,下面主要讲第二种方法
在配置中加入 splitChunks
const path = require('path');
module.exports = {
mode: 'development',
entry: {
app: "./src/index.js",
print: './src/print.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
测试: 例如在index.js和print.js里面都引用了lodash,最后webpack打包会把lodash单独在一个文件中。
其中最后一个文件就是loadash ,可以看到app.js打包都文件大小较少了很多。
下面测试下不提取公共包,webpack后都文件大小
会发现app.js 和print.js都非常大。
缓存
缓存是浏览器的一项技术,能提交网页记载速度。但是对于修改的文件,缓存会是很头疼的问题,会导致拿不到新资源。下面是利用优化缓存的一些办法:
output
如果我们的文件名不改变的化,会默认用缓存的文件名,这会导致拿不到最新的资源。output可以定义输出的文件名,[chunkhash]可输出一个内容相关的hash 内容改变的化hash也会改变,从而不会有缓存。配置如下
module.exports = {
...
entry: {
app: "./src/index.js"
},
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname,"../dist")
}
}
webpack后的文件名如下:
不改变文件内容,再webpack,这个hash也不会改变。
测试下改变内容后,文件名会改变,如下图:
如此达到了没有修改的文件从缓存中取,加快了网页加载速度,修改了的文件重新获取的效果。
提取模板
对于那些不会变的代码,可以提取出来,直接利用缓存,加快网页加载速度。例如:模版模块,第三方模块。
optimization.runtimeChunk
能够将运行时的代码提取出来,配置如下
module.exports = {
...
entry: {
app: "./src/index.js"
},
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname,"../dist")
},
optimization: {
runtimeChunk: "single"
}
}
不管app.js变动,runtime.js都不会改变。
splitChunks cacheGroups 分离第三方模块
第三方模块也是很少变动的模块,把他们分离出来,减轻app.js的大小,能有效利用缓存。
配置如下:
module.exports = {
...
entry: {
app: "./src/index.js"
},
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname,"../dist")
},
optimization: {
runtimeChunk: "single",
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all"
}
}
}
}
}
webpack的结果如下:
vendor这个文件是node_modules里面的第三方插件。
改变app.js文件,runtime和vendor也不会变。从而也利用了缓存。同时app.js也从1010k缩小到12.8k。