webpack做哪些事情
- 代码转换--把浏览器不支持的文件转换(如less,Sass,TypeScript)为浏览器支持的代码
- 文件优化--压缩代码,合并文件,减少体积等 优化点
- 代码分割--懒加载
- 模块合并--组件化
- 自动刷新--热更新
- 代码校验
- 自动发布
webpack的核心
-
- mode(开发环境)
-
- entry(入口)
-
- output(出口)
-
- module(模块)
- loader 模块转换器,用于把模块原内容转换成新内容
-
- plugin(插件)
- 扩展插件,在Webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情
-
- Chunk(代码块)
- 代码块,一个Chunk有多个模块组合而成,用于代码合并与分割
-
- devServer(开发服务)
-
- optimization(优化项)
-
- externals(不需要打包)
-
- devtool(源码映射)
-
- watch(实时监测)
Webpack 启动后会从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的 Loader 去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。
基础配置--mode
module.exports = {
// 开发模式,有两种(development/production) 默认为默认为production
mode: 'development',
}
基础配置--entry
单入口配置
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
// 入口文件,webpack执行构建的第一步从entry开始,可抽象成输入.
entry: './src/main.js'
}
多入口配置
module.exports = {
mode: 'development',
// 入口文件,webpack执行构建的第一步从entry开始,可抽象成输入.
entry: {
index: './src/index.js',
about: './src/about.js',
other: './src/other.js'
}
}
基础配置--output
对于单入口的output的输出配置
// HtmlWebpackPlugin把webpack输出的内容插入到页面上(后面会详细介绍)
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/main.js',
// 输出
output:{
// 打包后的文件名(产生的新文件)
filename: 'bundle.[hash].js',
// 打包后bundle.[hash].js放哪里,path路径必须是一个绝对的路径
path: path.resolve(__dirname,'dist'),
// publicPath输出解析文件的目录,指定资源文件引用的目录,打包后浏览器访问服务时的 url 路径中通用的一部分。
// publicPathic项则被许多webpack的插件用于在生产模式下更新内嵌到css、html文件里的url值。
// 如我们的文件要传到cdn上加速,这个可以publicPath可以直接配置我们cdn上的加速域名
publicPath: 'http://服务器的地址'
},
plugins:[
new HtmlWebpackPlugin({
template: './public/index.html', // 模板
filename: 'index.html' // 输出的名字
})
]
}
- 配置public
- 没有配置publicPath
对于多入口的output的输出配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
about: './src/about.js',
other: './src/other.js'
},
// 输出
output:{
// 打包后的文件名(产生的新文件) [name]指的是多入口(entry)的对象的key
filename: '[name].[hash].js', // 避免了缓存的问题
// 打包后bundle.[hash].js放哪里,path路径必须是一个绝对的路径
path: path.resolve(__dirname,'dist'),
},
plugins:[
new HtmlWebpackPlugin({
template: './public/index.html', // 模板
filename: 'index.html', // 输出的名字
chunks: ['index',], // 输出文件包含的代码块
}),
new HtmlWebpackPlugin({
template: './public/about.html', // 模板
filename: 'about.html', // 输出的名字
chunks: ['index','about'], // 输出文件包含的代码块
}),
new HtmlWebpackPlugin({
template: './public/other.html', // 模板
filename: 'other.html', // 输出的名字
chunks: ['other'], // 输出文件包含的代码块
}),
]
}
- index的打包输出
- about的打包输出
基础配置--module
解析css
- style-loader // 把css插入到head标签中(以style的标签)
- css-loader // 解析@import这种语法
- 安装 npm i style-loader url-loader --save-dev
module.exports = {
...
// 模块
module:{
rules:[ // 规则
{
test: /\.css$/,
// loader的解析顺序,从右到左,从下到上
// - 多个loader需要[]
// use: ['style-loader','url-loader']
// - loader也可以写成对象
use: [
{
loader: 'style-loader',
options:{
// attributes: '',
// injectType: '',
// base: '',
// esModule: ''
// insert: "top", // 插到哪里 (顶部)
// singleton:true 是否只使用一个style,会将页面上的style标签合成一个
}
},
'css-loader'
]
}
]
},
...
}
解析less,sass,stylus等css编译语言
less
- less
- less-loader
- 安装 npm install less less-loader --save-dev
sass
- sass
- node-sass
- sass-loader
- 安装 npm install sass node-sass sass-loader --save-dev
stylus
- stylus
- stylus-loader
- 安装 npm install stylus stylus-loader --save-dev
以less为例:
module.exports = {
...
// 模块
module:{
rules:[ // 规则
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
options:{}
},
{
loader: 'style-loader',
options:{
sourceMap: true // 源码映射,增加体积
}
},
]
},
{
test: /\.less/,
use: [
{
loader: 'style-loader',
options:{}
},
'css-loader',
'less-loader'
]
}
]
},
...
}
提取css
css通过style标签的方式添加到了html文件中,但是如果样式文件很多,全部添加到html中,难免显得混乱。这时候我们想用把css拆分出来用外链的形式引入css文件怎么做呢?这时候我们就需要借助插件来帮助我们
使用MiniCssExtractPlugin插件
- mini-css-extract-plugin
- 安装 npm i mini-css-extract-plugin --save-dev
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...
// 模块
module:{
rules:[ // 规则
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'style-loader',
options:{
sourceMap: true // 源码映射,增加体积
}
},
]
},
{
test: /\.less/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
]
}
]
},
plugins:[
new MiniCssExtractPlugin({
filename: 'css/main.css', // 输出的路径
chunkFilename: '[id].css',
})
]
}
给css加前缀
- postcss-loader
- autoprefixer
- 安装 npm i postcss-loader autoprefixer --save-dev 在webpack.config.js同级目录下建立一个文件postcss.config.js
module.exports = {
plugins: [require('autoprefixer')]
}
css加前缀
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...
// 模块
module:{
rules:[ // 规则
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options:{
sourceMap: true // 源码映射,增加体积
}
},
{
loader: 'postcss-loader',
// 这里可以在webpack.config.js同级下建立一个postcss.config.js文件
options:{
plugins: [
require('autoprefixed')
]
}
}
]
},
{
test: /\.less/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
'less-loader'
]
}
]
},
plugins:[
new MiniCssExtractPlugin({
filename: 'css/main.css', // 输出的路径
chunkFilename: '[id].css',
}),
]
}
如果这里添加前缀一直不生效,请看自己的package.json中是否配置如下:
{
"browserslist": [
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
}
排除不用的css
使用PurifyCSS可以大大减少CSS冗余
- purify-css
- purifycss-webpack
- 安装 npm i purifycss-webpack purify-css --save-dev
- 引入glob,因为我们需要同步检查html模板,所以我们需要引入node的glob对象使用。在webpack.config.js文件头部引入glob。
- 引入purifycss-webpack,配置plugins。
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PruifyCssPlugin = require('purifycss-webpack');
module.exports = {
...
// 模块
module:{
noParse: /jquery/, // 不去解析jquery中的依赖关系
rules:[ // 规则
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options:{
sourceMap: true // 源码映射,增加体积
}
},
{
loader: 'postcss-loader',
// 这里可以在webpack.config.js同级下建立一个postcss.config.js文件
options:{
plugins: [
require('autoprefixed')
]
}
}
]
},
{
test: /\.less/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
'less-loader'
]
}
]
},
plugins:[
new MiniCssExtractPlugin({
filename: 'css/main.css', // 输出的路径
chunkFilename: '[id].css',
}),
new PruifyCssPlugin({
// //public下所有的html
paths:glob.sync(path.join(__dirname,'public/*.html'))
})
]
}
解析js(es6,es7~es10)
- babel-loader
- 调取babel核心模块,进行解析js
- @babel/core
- 是babel转译器本身,提供转译的API,例如babel.transform等,webpack的babel-loader就是调用这些API完成转译的
- @babel/preset-env
- es6->es5
- @babel/polyfill
- JS标准新增的原生对象和API的shim,实现上仅仅是core-js和regenerator-runtime两个包的封装
- // 转换高级语法如'a'.includes('a')
- @babel/plugin-transform-runtime
- transform-runtime是将js中使用到新的原生对象和静态方法转译成对babel-runtime的引用,而其中babel-runtime的功能其实最终也是由core-js来实现的,其实真正的核心是上面所讲的core-js,其他的都是包装。
- @babel/plugin-proposal-decorators/@babel/plugin-proposal-class-properties
- 解析类的装饰器等 -安装
- npm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties --save-dev
- npm i @babel/polyfill --save
module.exports = {
...
// 模块
module:{
rules:[ // 规则
{
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 }],
// 解析如'aa'.include('a')
"@babel/plugin-transform-runtime"
]
}
},
// 表示哪些目录中的 .js 文件需要进行 babel-loader
include: path.resolve(__dirname,'src'),
// 表示哪些目录中的 .js 文件不要进行 babel-loader
exclude: /node_modules/, // 可以优化打包时间
}
]
},
plugins:[]
}
设置全局变量
module中rules中loader配置
使用到的loader
- expose-loader
- 安装 npm i expose-loader --save-dev
module.exports = {
...
// 模块
module:{
rules:[
// 设置全局jq
{
test: require.resolve('jquery'),
use: 'expose-loader?$', // 把jq设置为全局变量
},
]
},
plugins:[]
}
在入口文件中引入配置
// 暴露全局的loader
import $ from 'expose-loader?$!jquery';
console.log($) //
console.log(window.$); // 这样就挂载到window上了
解析资源如(png|jpeg|gif)等
- url-loader
- file-loader
- 安装 npm i url-loader file-loader --save
url-loader和file-loader的区别
- file-loader 可以指定要复制和放置资源文件的位置,以及如何使用版本哈希命名以获得更好的缓存。此外,这意味着 你可以就近管理图片文件,可以使用相对路径而不用担心部署时 URL 的问题。使用正确的配置,webpack 将会在打包输出中自动重写文件路径为正确的 URL。
- url-loader 允许你有条件地将文件转换为内联的 base-64 URL (当文件小于给定的阈值),这会减少小文件的 HTTP 请求数。如果文件大于该阈值,会自动的交给 file-loader 处理。
- 给图片配了 url-loader 在配置里面就不要再给图片配 file-loader 了 ,因为 url-loader 默认会使用 file-loader 来处理图片的路径关系的,只是加了个当图片太大把路径转成了base64的功能。
webpack配置解析规则
module.exports = {
...
// 模块
module:{
rules:[ // 规则
{
test: /\.(png|svg|jpg|jpeg|gif)$/,
loader: 'url-loader',
options:{
limit: 1024
}
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
loader: 'file-loader',
options: {
esModule: false // 注意esModule参数是表示是否使用es6模块的导出,默认是启用的,根据实际情况使用
}
}
]
},
plugins:[]
}
解析.vue文件
使用到的loader
- vue-loader
- 调取vue-style-loader,vue-template-compiler来解析.vue文件
- vue-style-loader
- .vue文件中style中样式加载器模块
- vue-template-compiler
- vue-template-compiler被vue-loader所引用:使用vue-template-compiler直接读取处理了.vue文件SFC内容,将.vue文件处理为一个AST。
- 安装 npm i vue-loader vue-style-loader vue-template-compiler --save-dev
const VueLoaderPlugin = require('vue-loader/lib/plugin') // vue-loader 插件,它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块
module.exports = {
...
module:{
rules:[
{
test: /\.vue$/,
use: ['vue-loader']
},
...
]
},
plugins:[
new VueLoaderPlugin()
]
}
解析配置react文件
使用到的loader
- @babel/preset-react
- 安装 npm i @babel/preset-react --save-dev
module.exports = {
...
module:{
rules:[
{
test: /\.js(x)$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
},
include: path.resolve(__dirname,'src'),
exclude: /node_modules/,
},
...
]
},
plugins:[]
}
plugins
html-webpack-plugin
通过html-webpack-plugin可以把生成的js代码以标签的形式插入到html代码中
- html-webpack-plugin
- 安装 npm i html-webpack-plugin --save-dev
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins:[
new HtmlWebpackPlugin({
template: './public/index.html', // 模板信息
filename: 'index.html', // 输出的名字
minify:{ // 最小化操作
removeAttributeQuotes: true, // 去双引号等
collapseWhitespace: true, // 折叠压缩到一行
},
hash: true, // 加版本
})
]
}
clean-webpack-plugin
每次执行npm run build会发现dist文件夹会残留上次打包的文件 我们可以使用clean-webpack-plugin先清空文件夹然后再输出打包.
- clean-webpack-plugin
- 安装 npm i clean-webpack-plugin --save-dev
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
...
plugins:[
new CleanWebpackPlugin()
]
}
copy-webpack-plugin
复制某个文件中的静态资源到哪里
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
...
plugins:[
new CopyWebpackPlugin([
{
from: './doc',
to: ''
}
]),
]
}
webpack中内置的plugins
webpack.BannerPlugin(打包声明)
const webpack = require('webpack');
module.exports = {
...
plugins:[
// 打包声明
new webpack.BannerPlugin('make 2019 by 哈哈'),
]
}
webpack.ProvidePlugin(给每个模块都注入$(jquery))
const webpack = require('webpack');
module.exports = {
...
plugins:[
new webpack.ProvidePlugin({
$: 'jquery', // 在每个模块中都注入$(jquery)符合
})
]
}
webpack.IgnorePlugin
用于忽略某些特定的模块,让webpack不把这些指定的模块打包进去
const webpack = require('webpack');
module.exports = {
...
plugins:[
//moment这个库中,如果引用了./locale/目录的内容,就忽略掉,不会打包进去
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
}
基础配置--devServer
devServer基础配置
module.exports = {
devServer:{ //开发服务器的配置
port: 2020, // 端口号
progress: true, // 加进度条
contentBase: './dist', // contentbase代表html页面所在的相对目录,如果我们不配置项,devServer默认html所在的目录就是项目的根目录,这个时候启动服务,访问地址通常会出现下面这样的场面
compress: true, // gizp压缩
},
}
devServer启动热更新
module.exports = {
devServer:{ //开发服务器的配置
port: 2020, // 端口号
progress: true, // 加进度条
contentBase: './dist', // contentbase代表html页面所在的相对目录,如果我们不配置项,devServer默认html所在的目录就是项目的根目录,这个时候启动服务,访问地址通常会出现下面这样的场面
compress: true, // gizp压缩
hot: true, // 开启热更新
// open 在devServer启动且第一次构建完成时,自动用我们的系统的默认浏览器去打开要开发的网页,
open: true,
},
plugins:[
new webpack.NamedChunksPlugin(), // 打印热更新的模块路径
new webpack.HashedModuleIdsPlugin(), // 热更新插件
]
}
devServer设置跨域(3种方式)
module.exports = {
devServer:{ //开发服务器的配置
// 1配置跨域请求
proxy:{ // 重写的方式,把请求代理到express是服务器上
'/api': {
target: 'http://localhost:3000',
pathRewrite: {
'/api':''
}, // 重写
}, // 配置了一个代理
},
// 2我们前端只想单纯模拟数据
before(app){ // webpack提供的方法
app.get('/user',(req,res)=>{
res.json({name:'哈哈哈11'})
})
},
//3有服务端,不用用代理来处理,能不能在服务端启动webpack端口用服务端端口
/*
服务端处理
*
let middleware = require('webpack-dev-middleware')
let config = require('./webpack.base.js');
let compiler = webpack(config)
app.use(middleware(compiler))
* */
}
}