webpack是一个模块打包机,它会从指定入口文件开始,递归的寻找JavaScript模块以及其余一些浏览器没法直接运行的扩展语言(Sass, TypeScript)等,将其打包成合适的格式以供浏览器使用。
它的做用有代码转换(利用各类loader将浏览器没法识别的语言转换成合适的格式),文件优化(好比说打包时压缩体积),代码分割,模块合并,自动刷新(热更新),代码校验,自动发布。 引用了网上的一张图来大体看一下webpack的运行机制: css
首先是安装webpack,在本地项目文件夹下npm init初始化以后,下载webpack以及webpack-cli:html
npm init -y
npm i webpack webpack-cli -D
复制代码
此时在文件夹下创建一个src文件夹,用于放置项目代码。webpack此时能够进行0配置打包,在命令行输入npx webpack
能够打包出一个dist文件夹,下面有一个main.js就是打包后的文件。这个打包后的文件内容,就是使用递归的方式解析src中的js模块,递归的方法名为__webpack_require__,它支持咱们在浏览器中使用CommonJs规范。
默认打包的配置很弱,它只能识别js模块,在没有配置的状况下,webpack就至关于一个js模块打包机。固然咱们不可能直接就0配置打包一个项目,下面我总结一下webpack中经常使用的一些基本配置。前端
webpack中默认的配置文件名为webpack.config.js,在根目录下创建一个名为webpack.config.js的文件,就能够在这个文件中写配置项。它的内容遵循CommonJs规范,webpack提供给咱们修改这个文件名的一些方法:
(1)打包时输入命令npx webpack --config webpack.config.my.js
。
(2)为了避免用每次都在命令行输出一串这么长的命令,在package.json中配置scripts,"build" : "webpack --config webpack.config.my.js"
。
这两种配置方法均可以修改默认配置文件名。先在webpack.config.my.js写一段基本的配置:node
//webpack是用node写的
let path = require('path');
module.exports = {
mode: 'development', //模式 生产环境production 开发模式development
entry: './src/index.js', //入口
output: { //出口
filename: 'bundle.[hash:8].js', //打包后的文件名,[hash]每次打包生成新的文件
//__dirname以当前目录解析成绝对路径
path: path.resolve(__dirname, 'dist'), ///path字段只接受绝对路径,所以须要一个node模块来辅助配置 path.resolve把相对路径解析成绝对路径
publicPath: 'http://www.help.com'//公共路径,打包出的资源文件会带着这个公共路径。
}
}
复制代码
loader帮助咱们告诉浏览器遇到不能识别的模块应该怎么处理,前面咱们说过webpack默认配置只是别js模块,那么解析图片、css、less这些模块就须要引入loader。react
图片引入有三种方式:jquery
const logo = require('./01.jpg') //把图片引入,返回的结构是一个新的图片地址
const image = new Image();
console.log(logo); //用到file-loader 默认会在内部生成一张图片,到build目录下 把生成的图片的名字返回回来
image.src = logo;
document.body.appendChild(image)
复制代码
配置以下:webpack
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
name:[name].[ext],
outputPath: '/img/' //打包时另外生成一个img文件夹
}
}
},
复制代码
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 200 * 1024,//小于200k使用url-loader,大于200k使用file-loader
outputPath: '/img/'
}
}
},
复制代码
body {
background: red;
background: url('./01.jpg')
}
复制代码
<img src="./01.jpg" alt="">
复制代码
配置以下:ios
{
test: /\.html$/,
use: 'html-withimg-loader'
}
复制代码
打包CSS文件,咱们须要用到两个loader,一个是style-loader,它负责处理css文件中的import、url()语法。style-loader之内联<style>
的形式将样式都写到模版html的<head>
头部中。打包LESS文件一样的套路,less-loader现将less转换成css。
配置以下:nginx
module: {
rules: [
//loader的用法。字符串只用一个loader 多个loader须要用数组 loader的顺序 默认从右向左 从下往上执行
{
test: /\.css$/,
use: [{
loader: 'style-loader',
options: {
insertAt: 'top'//插在最上面,让本身写在模版html<style>标签中的样式优先级较高
}
}, 'css-loader']
},
{
test: /\.less$/,
use: [{
loader: 'style-loader',
options: {
insertAt: 'top'
}
}, 'css-loader', 'less-loader']
}
]
}
复制代码
以下图所示,head标签下面的三个样式是分别在.css和.less文件中定义的样式,而<head>
标签上面的一个样式是在模版html中本身设定的。 es6
用一个autoprefixer包和一个postcss-loader自动添加浏览器前缀,且这个插件是会更新的,之前transform在须要加上webkit前缀,但Chrome支持后postcss-loader就不会再给这个属性加上前缀了。
npm i postcss-loader autoprefixer -D
//在根目录建立postcss.config.js文件
module.exports = {
plugins: [require('autoprefixer')]
}
//在rules css配置中新加入postcss-loader
module: {
rules: [
//loader的用法。字符串只用一个loader 多个loader须要用数组 loader的顺序 默认从右向左 从下往上执行
{
test: /\.css$/,
use: [{
loader: 'style-loader',
options: {
insertAt: 'top'//插在最上面,让本身写在模版html<style>标签中的样式优先级较高
}
}, 'css-loader']
}
]
}
复制代码
Plugin能够在webpack运行到某个阶段的时候,帮助咱们作某些事情,相似于生命周期的概念。在某个时间点,须要某个机制完成一些事情。
在咱们打包文件后,该插件会生成一个html模版,而且把打包后的其余文件在该模版中引用。生成的html模版的内容是咱们能够本身定义的。
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html', //模版文件的位置
filename: 'index.html', //打包出来html文件的名称
minify: {
removeAttributeQuotes: true, // 去除双引号
collapseWhitespace: true, //变成一行
},
hash: true //添加一个hash戳
})
],
复制代码
咱们打包时,把全部样式抽离出来生成一个CSS文件,在模版html文件中以link形式引入。
npm i mini-css-extract-plugin -D
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
plugins: [
new MiniCssExtractPlugin({
filename: 'main.css'
})
]
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, //把style-loader换成MiniCssExtractPlugin.loader
'css-loader',
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
'postcss-loader'
]
}
]
}
复制代码
进行到这一步会发现,生产模式下打包出来的main.css也没有被压缩,是由于用了MiniCssExtractPlugin这个插件不会压缩css,须要本身压缩。使用OptimizeCSSAssetsPlugin插件配置优化项,可是使用这个插件以后,css确实压缩了,但js又不会压缩了,所以还要用到UglifyJsPlugin再来压缩js。
npm i optimize-css-assets-webpack-plugin -D
npm i uglifyjs-webpack-plugin -D
const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); //压缩js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); //压缩css
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true, //缓存
parallel: true, //并发压缩
sourceMap: true // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
}
复制代码
在没有使用该插件以前,每次打包上一版本的文件会遗留在dist文件夹下,须要咱们手动删除。这个插件,在每次打包以前,先把以前的dist文件夹删除,打包生成新的dist目录。
npm install --save-dev clean-webpack-plugin
const {CleanWebpackPulgin} = require('clean-webpack-plugin')
plugins: [
new CleanWebpackPlugin()
]
复制代码
源代码与打包后的代码的映射关系,帮助咱们定位错误在源代码中的位置。在devtool字段中配置,推荐的配置以下:
devtool:"cheap-module-eval-source-map" //开发环境
devtool:"cheap-module-source-map" //线上生产环境
复制代码
一个提高开发效率的利器,每次改完代码都要从新打包一次,刷新浏览器很是的麻烦。用webpack-dev-server搭建一个服务器,使得咱们不用真实的打包,而是在内存中打包,放置到devSever服务器上,以便咱们在开发时调试测试整个项目。
下载webpack-dev-server:
npm i webpack-dev-server -D
复制代码
以后,先在package.json中配置scripts,"dev" : "webpack-dev-server --config webpack.config.my.js"
。而后配置一下devServer中的一些配置项:
devServer: { //开发服务器的配置
port: 8889, //端口号
progress: true, //进度条
contentBase: './dist', //指定了服务器资源的根目录,可是在开发过程不会真实打包
compress: true, //启用 gzip 压缩
open: true //自动打开浏览器
},
复制代码
联调期间,先后端分离,直接获取数据会跨域,上线后咱们使⽤用nginx转发,开发期间,webpack-dev-server就能够搞定这件事。
咱们先启动服务器,mock一个接口:
const koa = require('koa')
const app = new koa()
const Router = require('koa-router')
const router = new Router()
router.get('/api/info', async (ctx, next) => {
ctx.body = {
username: 'zhunny',
message: 'hello mock'
}
ctx.status = 200
})
app.use(router.routes())
app.listen(3000)
复制代码
此时在咱们前端项目中请求该接口的数据,会存在跨域问题,咱们在dev-server中配置服务器代理:
axios.get('http://localhost:3000/api/info').then(res=>{
console.log(res)
})
复制代码
devServer: { //开发服务器的配置
port: 8889, //端口号
progress: true, //进度条
contentBase: './dist', //指定了服务器资源的根目录,可是在开发过程不会真实打包
compress: true, //启用 gzip 压缩
open: true, //自动打开浏览器
proxy: {
"/api": {
target: "http://localhost:3000"
}
}
},
复制代码
以后修改请求的接口:
axios.get('/api/info').then(res=>{
console.log(res)
})
复制代码
webpack自己能够处理ES6语法,可是有些浏览器对es六、es7或者是es8的语法还不能识别。出于兼容性的考虑,咱们会使用Babel来将ES6转换成ES5语法。
npm i babel-loader @babel/core @babel/preset-env -D
{
test: /\.js$/,
exclude: /node_modules/, //排除该文件夹下的内容
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
}
复制代码
固然只配置上述字段是不够的,到此步为止,一些es六、七、8新增的方法和类依然不能被识别。咱们还须要下载@babel/polyfill
,它将es六、七、8中的语法特性打包放到浏览器中,至关于一个补丁包。
它的基本使用方法是在入口js文件中引用:import '@babel/polyfill'
。可是这种方法是引入整个补丁包,使得webpack打包后的体积变大。咱们能够对这点进行优化。移除在js文件中引用的@babel/polyfill
,配置文件中添加useBuiltIns字段,对使用到的es六、七、8语法特性按需加载。
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',//按需加载
corejs: 2 //指定core的版本
}
]
],
复制代码
当咱们开发组件库、工具库这些场景时,在js文件中引用@babel/polyfill
就不合适了。由于@babel/polyfill
以全局变量的方式注入,会形成全局污染。上面用的按需加载usage的方法也不会形成全局污染,可是这个字段还在试验阶段。咱们可使用闭包方式@babel/plugin-transform-runtime
来代替。可是这种方式就不会对原型链上的某些方法进行转义,所以开发正常的业务场景就比较适合用polyfill,无所谓全局变量的影响,咱们不须要担忧某些原型链上的方法没有被转义。
npm i @babel/plugin-transform-runtime -D
npm i @babel/runtime -S
{
test: /\.js$/,
exclude: /node_modules/, //排除该文件夹下的内容
use: {
loader: 'babel-loader',
options: {
plugins: [
[
'@babel/plugin-transform-runtime',
{
absoluteRuntime:false,
corejs:2,
helpers:true,
regenerator:true,
useESMoudules:false
}
]
]
}
}
}
复制代码
Babel配置可能内容较多,咱们能够把options内容放到.babelrc中。
//.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage", //按需加载 实验性的功能
"corejs": 2
}
],
"@babel/preset-react"
]
}
复制代码
一些es7的提案如class,则还须要用到@babel/plugin-proposal-class-properties,装饰器则须要用到@babel/plugin-proposal-decorators:
npm i @babel/plugin-proposal-class-properties -D npm i @babel/plugin-proposal-decorators -D {
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
],
plugins: [
['@babel/plugin-proposal-decorators', { "legacy": true }],
['@babel/plugin-proposal-class-properties', { "loose": true }],
['@babel/plugin-transform-runtime'] //generator
]
}
}
}
复制代码
js语法的校验用到了eslint以及eslint-loader,他的官网为https://eslint.org
,eslint在使用时须要配置一个.eslint.json的规则文件放在根目录,具体配置项见官网。
npm i eslint eslint-loader -D
{
test: /\.js$/,
use: {
loader: 'eslint-loader',
options: {
enforce: 'pre' //在普通loader以前执行
}
},
}
复制代码
引入全局变量有三种方式,假如要在全局引入jquery库:
import $ from 'jquery'
require(expose-loader)
console.log(window.$)
复制代码
new webpack.providePlugin(
{$:'jquery'}
)
复制代码