原文链接:banggan.github.io/2019/05/09/…
Webpack核心概念解析
终于忙完了论文,可以愉快的开始学习了,重拾起重学前端、webpack以及Vue的源码解读作为入职前的复习吧。整个webpack系列将分成五个大的部分进行,以webpack4.0为文档进行解读,从简单的概念解读到最后的实现。 整个知识点涉及范围:

loader
使用loader来预处理文件,把不同的静态资源(模块的结尾不是js的模块)打包成js文件
loader打包静态资源
打包图片
- 安装使用file-loader实现:
npm install file-loader -D
- 在webpack.config.js中添加loader的配置
module.exports = {
//打包项目的入口文件
entry: './src/index.js',
module:{
rules:[{
test:/\.(jpg|png|gif)$/,//打包以jpg、png、gif结尾的所有图片文件
use:{
loader:'file-loader',
options:{//placeholder 占位符
name:'[name]_[hash].[ext]',//保持原图片的名字+hash值和后缀,主要单引号
outputPath:'image/'//打包图片的位置
}
}
}]
}
}
- 更多的有关于file-loader的配置见文档
打包图片成base64格式
url-loader基本能实现file-loader的打包功能,适用于小图片的打包
- 好处:图片打包成js文件,不用加载图片的地址,页面快速显示
- 坏处:图片过大导致js文件过大
所以,当图片的大小小于limit值时会把图片打包成base64格式,大于limit值则按照file-loader打包成图片文件
- 安装使用url-loader实现:
npm install url-loader -D
- 在webpack.config.js中添加loader的配置
module.exports = {
module:{
rules:[{//打包以jpg、png、gif结尾的所有图片文件
test:/\.(jpg|png|gif)$/,
use:{
loader:'url-loader',
options:{//placeholder 占位符
name:'[name]_[hash].[ext]',//保持原图片的名字+hash值和后缀,主要单引号
outputPath:'image/',//打包图片的位置
limit:2048
}
}]
}
}
- 更多的有关于url-loader的配置见文档
打包样式css文件
需要使用css-loader、style-loader
- css-loader:分析几个css文件的关系,合并css文件
- style-loader:将css-loader合并的css内容挂载在页面的head部分
实现方式:
- 安装loader实现:
npm install css-loader style-loader -D
- 在webpack.config.js中添加loader的配置
module.exports = {
module: {
rules: [{//打包css文件
test:/\.css$/,
use:['style-loader','css-loader']
}]
}
}
- 更多的有关于css-loader的配置见文档
打包样式scss文件
需要使用sass-loader、node-sass
- 安装loader实现:
npm install sass-loader node-sass -D
- 在webpack.config.js中添加loader的配置
module.exports = {
module: {
rules: [{
test: /\.scss$/,
use:['style-loader','css-loader','sass-loader']
}]
}
};
在配置中,有三个loader,执行顺序是从下到上,从右到左。在打包scss文件时,首先执行sass-loader:对sass翻译成css文件,在挂载到css-loader,最后style-loader.
- 更多的有关于sass-loader的配置见文档
为样式添加不同浏览器的前缀
为了兼容不同的浏览器,在写样式的时候需要加上适用不同浏览器的前缀,如-o、-webkit、-moz等
-安装loader实现:npm install postcss-loader autoprefixer -D
-在根目录创建postcss.config.js
moudle.exports ={
plugins:[
require('autoprefixer')
]
}
- 在webpack.config.js中添加loader的配置
module.exports = {
module: {
rules: [{
test:/\.scss$/,
use:[
'style-loader',
'css-loader',
'sass-loader',
'postcss-loader']
}]
}
}
- 更多的有关于postcss-loader的配置见文档
css-loader添加不同的配置
css模块化打包
- 场景:在文件引入的scss不仅影响当前的文件,还影响当前文件引入的其他js文件,造成样式冲突
- 实现:css只在当前模块类有效,在配置中添加
modules:true
开启css的模块化打包,在引入的时候注意区分
scss文件的嵌套引用
-
场景:scss文件通过import引入其他scss文件,导致打包的时候引入的scss文件打包错误
-
实现:
importLoader:2
-
在webpack.config.js中添加loader的配置
module:{
rules:[{//打包scss文件
test:/\.scss$/,
use:[
'style-loader',
{
loader:'css-loader',
options:{
importLoaders:2,//index.scss中通过import引入其他的scss文件,引入的scss文件在打包的时候也将依次经过所有的loader
modules:true
}
},
'sass-loader',
'postcss-loader']
}]
}
打包字体图标文件
在阿里巴巴矢量图标库中,把需要的字体图标下载到本地,解压。将iconfont.eot iconfont.svg iconfont.ttf iconfont.woff 四种图片文件放入到项目中,在src中新建一个放字体图标的文件夹font。将iconfont.css文件拷贝到项目中,修改对应字体的引用路径。
- 需要安装 file-loader:
npm i file-loader -D
- 在webpack.config.js中添加loader的配置
module.exports = {
...
module: {
rules: [{
test: /\.(eot|ttf|svg|woff)$/,
use:{
loader:'file-loader'
}
},
]
}]
}
}
- 更多的有关于加载字体的配置见文档
打包数据文件
如遇到json、scv、xml文件需要打包时,使用csv-loader 和 xml-loader实现。
- 安装:npm install csv-loader xml-loader -D
- 在webpack.config.js中添加loader的配置
module.exports = {
module: {
rules: [{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
{
test: /\.xml$/,
use: [
'xml-loader'
]
}]
}
}
- 更多的有关于加载数据的配置见文档
plugins
loaders可以将各个类型的静态资源打包成webpack能处理的模块,而plugins有更强大的功能。它可以从打包优化和压缩,一直到重新定义环境中的变量。
plugin可以在webpack运行到某一个时刻,自动完成一些事情。
自动生成html文件,并引入打包生成的js文件到生成的html文件中
- 安装使用HtmlWebpackPlugin实现:
npm install html-webpack-plugin -D
- 在webpack.config.js中添加loader的配置
在src中创建一个html的模板,在HtmlWebpackPlugin的配置中引入该模板,打包后生成和模板类似的html文件并引入打包的js文件。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
})]
}
- 更多的有关于HtmlWebpackPlugin的配置见文档
自动清除上一次打包的dist文件
先删除上一次打包的dist文件,再执行打包
- 安装使用CleanWebpackPlugin 实现:
npm install clean-webpack-plugin -D
- 在webpack.config.js中添加loader的配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']), // 在打包之前,可以删除dist文件夹下的所有内容
]
}
- 更多的有关于CleanWebpackPlugin的配置见文档
Entry与Output的基础配置
- 需求:打包多个入口文件,对应的在出口的配置中注意命名filename避免出口文件名字冲突----使用占位符)来确保每个文件具有唯一的名称
- 需求:打包后的文件,作为后端的接口文件,静态资源需要上传到cdn,在output中进行配置,提前加入cdn的地址。在编译时,如果不知道publicPath的地址,可以留空,在入口起点文件运行时动态设置。
__webpack_public_path__ = myRuntimePublicPath
module.exports = {
mode: 'development',
entry: {
main: './src/index.js',
sub: './src/index.js'
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist'])],
output: {
publicPath: 'http://cdn.com.cn', //加入cdn地址
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
- 更多的有关于output的配置见文档
SourceMap的配置
SourceMap是一个映射关系,打包文件和源文件的映射关系,用于开发者的调试。
在devtool中进行设置:devtool: 'source-map'
打包速度会降低,在dist里面会有map映射文件
常用设置devtool说明:
- none:在开发者模式下,默认开启sourcemap,将其关闭
- inline前缀:不单独生成map文件,把对应的map文件以base64的形式直接打包到js文件。
- cheap前缀:sourcemap和打包后的js同行显示,并没有映射到列忽略源自 loader 的 source。 map,并且仅显示转译后的代码,所以打包速度相对来说较快。代码出错提示不用精确显示第几行的第几个字符出错,只显示第几行出错,会提高一些性能,
- moudle前缀:不仅映射业务代码,还会包括loader、第三方模块的错误。
- eval前缀:打包速度最快。
development环境推荐使用: devtool: 'cheap-module-eval-source-map'
production环境推荐使用: devtool: 'cheap-module-source-map'
- 更多的有关于devtool文档
使用WebpackDevServer提升开发效率
场景:每次在src里编写完代码都需要手动重新运行 npm run bundle,如何自动解决?
-安装:npm install webpack-dev-server –D
- devServer参数说明
- contentBase :配置开发服务运行时的文件根目录
- open :自动打开浏览器
- host:开发服务器监听的主机地址
- compress :开发服务器是否启动gzip等压缩
- port:开发服务器监听的端口
- 在 webpack.config.js 中,加 devServer
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
open: true,
port: 8080
proxy:{//配置跨域,访问的域名会被代理到本地的3000端口
'/api': 'http://localhost:3000'
}
}
}
- 在 package.json 中配置
{
"name": "banggan",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"bundle": "webpack",
"watch": "webpack --watch",
"start": "webpack-dev-server",
},
}
- 更多的有关于devServer文档
如何实现自己写一个类似webpackdevserver的工具
- 在package.json 中配置
创建一个新的指令,
npm run server
运行自己写的类似webpackdevserver的工具
{
"name": "banggan",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"bundle": "webpack",
"watch": "webpack --watch",
"start": "webpack-dev-server",
"server" : "node server.js"
},
}
- 安装express:
npm install express webpack-dev-middleware -D
- 在根目录创建一个server.js
- 在node中直接使用webpack:
webpack(config)
const express = require('express'); //引入express
const webpack = require('webpack');//引入webpack
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');//引入配置文件
const complier = webpack(config); //编译一次吗,打包一次代码
const app = express();//创建express实例
app.use(webpackDevMiddleware(complier, {}));
app.listen(3000, () => {//监听3000端口
console.log('server is running');
});
Hot Module Replacement热模块更新
-
场景:在程序运行中。替换、添加、替换某个模块,不需要重新加载整个页面,实现交互时更新。
-
在 webpack.config.js 中,添加配置
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true,//开启热更新功能
hotOnly: true//如果html功能没有实现,也不让浏览器刷新
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']),
new webpack.HotModuleReplacementPlugin()//使用热模块插件
],
}
- 在main.js文件中,使用 HotModuleReplacementPlugin 启用模块的热替换功能。接口暴露在moudle.hot属性下面
//如果模块启用了HMR,就可以用 module.hot.accept(),监听模块的更新。
if (module.hot) {
module.hot.accept('./library.js', function() {
// 使用更新过的 library 模块执行某些操作...
})
}
//拒绝给定依赖模块的更新,使用 'decline' 方法强制更新失败。
module.hot.decline(
dependencies // 可以是一个字符串或字符串数组
)
注意:引入css文件时,用框架Vue,React 时,不需要写 module.hot.accept(),因为在使用css-loader,vue-loader,babel-preset时,均配置好了HMR,不需要自己重新写 如果文件没有内置HMR,需要自己手动写监听模块更新代码
结合Bable处理ES6语法
- 场景:代码中含有ES6/ES7的代码,为了让低版本的浏览器兼容代码,需要使用Bable进行转换
- 安装:Bable官网
//preset-env语法转换
npm install babel-loader @babel/core @babel/preset-env -D
//兼容低版本浏览器的语法,函数的补充
npm install --save @babel/polyfill
- 在 webpack.config.js 中,添加配置
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, //排除在外:在node_modules中的js,babel-loader不生效
loader: "babel-loader" ,
options:{
"presets": [["@babel/preset-env",{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},//运行在大于**版本的浏览器上,,已经支持es6的高浏览器不需要转换为es5
useBuiltIns:'usage' //按需添加polyfill,把业务代码中的新语法新函数都转成低版本浏览器兼容的
}]]
}
}
]
}
- 在src目录下的index.js中顶部位置导入
import "@babel/polyfill";
注意如果不是打包业务代码,而是写的类库、或者z组件库的时候不能使用@babel/polyfill实现,因为会导致声明的变量变成全局变量,污染全局环境。使用
plugin-transform-runtime
实现
- 安装插件
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2
- 在 webpack.config.js 中,添加配置
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
options:{
"plugins": [["@babel/plugin-transform-runtime",{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}]]
}
}
]
}
由于babel配置的内容较多,官网推荐在根目录下创建属于babel的配置文件.babelrc文件
由于babel需要配置的内容非常多,我们需要在项目根目录下创建一个 .babelrc 文件。 就不需要在 webpack.config.js 中写 babel 的配置了。 在 .babelrc 中:
{
"plugins": [["@babel/plugin-transform-runtime", {
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}]]
}
总结
- 在webpack.config.js中
module.exports = {
mode: 'development', //开发环境进行打包,打包的代码不会压缩
devtool: 'cheap-module-eval-source-map',//不带列信息,只对业务代码进行sourcemap的生成
entry: {//配置入口文件
main: './src/index.js'
},
devServer: {//配置webpack,开发环境的调试
contentBase: './dist',//启动服务器的目录
open: true,//打开新的端口号8080
port: 8080,
hot: true,//打开热替换功能
hotOnly: true
},
module: {//对不同的文件进行打包规则
rules: [{
test: /\.js$/, //对js文件的babel-loader打包,其配置在.babelrc文件中
exclude: /node_modules/, //排除在外:在node_modules中的js,babel-loader不生效
loader: 'babel-loader',
}, {
test: /\.(jpg|png|gif)$/,//对图片进行打包
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240//小于10240以base64的形式进行打包
}
}
}, {
test: /\.(eot|ttf|svg)$/,//字体文件的打包
use: {
loader: 'file-loader'
}
}, {
test: /\.scss$/,//scss文件的打包,先用postcss-loader,在用sass-loader进行解析,最后css-loader进行挂载
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,//css文件的打包,没有sass-loader的解析
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']),//自动清空上一次打包
new webpack.HotModuleReplacementPlugin()//热替换插件
],
output: {//出口文件
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}