Webpack4
文章目录
webpack默认配置
文章目录
1. npm init -y
2. npm install webpack webpack-cli -D
3. 创建src / index.js 文件
运行webpack打包,使用npx 会在 node_module里面查找webpack模块
3. npx webpack
打包完成后 会生成 dist/main.js 文件
在根目录
创建webpack.config.js文件
默认配置 :
// webpack 是基于 nodejs的
// 要使用CommonJS规范导出一个对象
const path = require('path')
module.exports = {
// webpack 执行入口
entry: './src/index.js',
// 输出
output: {
// 输出到哪里,必须是绝对路径
path: path.resolve(__dirname, './dist'),
filename: 'main.js'
},
// 开发模式
mode: 'development'
}
如果不在根目录
创建webpack.config.js文件,默认是如下配置
-
默认入口模块
- ./src/index.js
-
默认输出
- 名称是main.js
- 路径是’./dist’
-
webpack默认支持多种模块类型:commonjs esmodule AMD
- esmodule 和 commonjs
- import { } from ‘./’
- const xxx = require(‘xxx’)
- webpack默认支持js模块和json模块
- esmodule 和 commonjs
-
注意:
- loader存在执行顺序:自右往左,自下往上
图片 / 字体资源打包
npm install file-loader -D
./dist/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="main.js"></script>
</body>
</html>
index.js
import pic from './asset/hz.jpg'
// 处理完成后 pic 即为 输出到路径的图片
// images/a4d6987a21c45234dc8a7e43044f33eb.jpg
console.log(pic)
var img = new Image()
img.src = pic
var root = document.querySelector('#root')
root.append(img)
webpack.config.js
module: {
rules: [
// 处理图片
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
//! 定义输出名字 具体查看官方文档 placeholders
name: '[name]_[hash:8].[ext]',
outputPath: 'images/',
}
}
]
},
// 处理字体
{
test: /\.(woff2|woff|ttf|eot|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]',
outputPath: 'font/',
}
}
]
},
]
}
此时页面就能显示出图片
css / less / …解析
npm install css-loader style-loader less less-loader -D
index.js
import("./asset/css/index.css")
import("./asset/css/a.css")
import("./asset/css/b.less")
webpack.config.js
// 处理css
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
//! 将多个样式文件打包到一个 style 标签里面
options: { injectType: 'singletonStyleTag' },
},
{
loader: 'css-loader'
}
]
},
// 处理less
{
test: /\.less$/,
use: [
{
loader: 'style-loader',
//! 将多个样式文件打包到一个 style 标签里面
options: { injectType: 'singletonStyleTag' },
},
{
loader: 'css-loader'
},
{
loader: 'less-loader'
}
]
},
postcss: css兼容性处理
使用 postcss 库 以及 autoprefixer 插件
npm install postcss-loader autoprefixer -D
- 第一种
webpack.config.js,在css处理 / less处理中 加入post-loader
{
loader: 'postcss-loader'
}
新建postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')({
// 配置兼容市场份额大于1%的浏览器最近两个版本
overrideBrowserslist: ['last 2 versions', '>1%']
})
]
}
- 第二种
涉及到postcss这个库。所以需要使用postcss-loader,同时还要用到postcss-preset-env插件
postcss-preset-env
包括autoprefixer
,所以在使用preset
时候不需要使用autoprefixer
npm i postcss-loader postcss-preset-env
// webpack.config.js
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [require('postcss-preset-env')()],
},
},
此外还需要在 package.json 中加入浏览器列表配置(推荐写在 package.json 中),除外还有很多种配置写法。
"browserslist": {
"development": [
"last 10 version"
],
"production": [
">0.01%",
"not dead",
"not op_mini all"
]
}
plugins
plugin可以在webpack运行到某个阶段的时候,帮你做一些事情,类似于生命周期的概念
配置html
使用插件:HtmlWebpackPlugin
npm install --save-dev html-webpack-plugin
HtmlWebpackPlugin
简化了HTML文件的创建,以便为你的webpack包提供服务。这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。 你可以让插件为你生成一个HTML文件,使用lodash模板提供你自己的模板,或使用你自己的loader。
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
new HtmlWebpackPlugin({
title: 'my app',
template: './src/index.html',
filename: 'index.html'
})
]
创建html文件作为模板 ./src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%=htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
重新构建到dist
使用插件:cleanWebpackPlugin
作用:在每次进行构建的时候 将dist删除重新构建新的dist文件
npm install --save-dev clean-webpack-plugin
webpack.config.js
const {CleanWebpackPugin} = require('clean-webpack-plugin')
plugins:[
new CleanWebpackPugin()
]
提取 css 成单独文件
mini-css-extract-pugin
npm install --save-dev mini-css-extract-plugin
将多个css样式文件提取成一个独立文件
webpack.config.js
const miniCssExtractPlugin = require('mini-css-extract-plugin')
module: {
rules: [
{
test: /\.less$/,
use: [
//! 将多个样式文件打包到一个 style 标签里面
// {
// loader: 'style-loader',
// options: { injectType: 'singletonStyleTag' },
// },
//! 将样式文件提取成一个独立文件
{
loader: miniCssExtractPlugin.loader,
options: {
}
},
'css-loader',
'postcss-loader',
'less-loader'
]
},
]
},
plugins:[
new miniCssExtractPlugin({
filename: '[name]_[chunkhash:8].css',
})
]
压缩css
使用插件:optimize-css-assets-webpack-plugin
// new一个对象即可,默认配置足够压缩css代码了
new OptimizeCssAssetsWebpackPlugin();
压缩 html 和 js
压缩 js: 只需要将 mode 改成production就可以了 压缩 html: 使用HtmlWebpackPlugin这款插件
plugin: {
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComments: true,
},
});
}
devServer
- 提升开发效率的利器
每次改完代码都需要重新打包一次,打开浏览器刷新,很麻烦。可以改善这块的体验 - 安装
npm install webpack-dev-server -D
- 配置
1.在package.json配置
"scripts": {
"server": "webpack-dev-server"
},
2.在webpack.config.js中配置
devServer: {
contentBase: "./dist",
port: 3000,
open: true,
// 设置 proxy 代理 处理跨域
proxy: {
'/api': {
target: 'http://localhost:3030'
// 当前想访问的接口是localhost:3030/api/info
// 配置之后访问 '/api/info'
}
}
},
- 启动
npm run server
启动服务后,会发现dist目录没有了,这是因为devServer把打包后的模块不不 会放在dist目录下,而是放到内存中,从而提升速度
- 本地mock,解决跨域:联调期间,前后端分离,直接获取数据会跨域,上线后我们使nginx转发,开发期间,webpack就可以搞定这件事
devServer:{
proxy: {
'/api': {
target: 'http://localhost:3030'
// 当前想访问的接口是localhost:3030/api/info
// 配置之后访问 '/api/info'
}
}
}
HMR
- 第一个版本
热替换模块(Hot Module Replacement))
作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块) 极大提升构建速度。
官方提醒 : 永远不要在生产环境(production)下启用 HMR
注意启动HMR后,css抽离会不生效,还有不支持contenthash, chunkhash
HMR支持style-loader css处理方式,不支持抽离成独立文件(minicssextractplugin)的方式 ,因此css在 style -loader 的作用下 做如下配置即可。
webpack.config.js
const webpack = require('webpack')
devServer: {
contentBase: "./dist",
port: 3000,
open: true,
//! 即使HMR不生效 浏览器也不会自动刷新,就开启hotOnly:true
hotOnly: true,
proxy: {
'/api': {
target: 'http://localhost:3030'
// 当前想访问的接口是localhost:3030/api/info
// 配置之后访问 '/api/info'
}
}
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
index.js
import('./asset/css/index.css')
var btn = document.createElement('button')
btn.innerHTML = '新增'
document.body.appendChild(btn)
btn.onclick = function () {
var div = document.createElement('div')
div.innerHTML = 'item'
document.body.appendChild(div)
}
index.css
div:nth-of-type(odd) {
background: red;
}
点击按钮生成隔行变色的div后,改变样式,div不会消失,而样式发生了改变,说明没有刷新页面,只是进行了模块的更新替换
- 第二个版本
开启热更新:
devServer: {
contentBase: resolve(__dirname, 'build'),
open: true,
port: 9090,
// 开启HMR功能
hot: true,
compress: true,
},
关于js模块的 HMR**
需要使用module.hot.accept来观察模块更更新 从而更新。手动监听所需要HMR的模块,当它的内容发生改变触发回调函数进行监听
add.js
function add () {
var op = document.createElement('p')
op.setAttribute('id', 'op')
op.innerHTML = 66
document.body.appendChild(op)
}
export default add
index.js
import('./asset/css/index.css')
var btn = document.createElement('button')
btn.innerHTML = '新增'
document.body.appendChild(btn)
btn.onclick = function () {
var div = document.createElement('div')
div.innerHTML = 'item'
document.body.appendChild(div)
}
// js模块 HMR 需要手动监听需要HMR的模块,当它的内容发生改变触发回调
import add from './add.js'
add()
if (module.hot) {
module.hot.accept('./add.js', () => {
console.log('add.js文件发生了改变')
document.body.removeChild(document.getElementById('op'))
add()
})
}
点击按钮新增div之后,改变add.js中的数字,页面的div没有消失,而p标签的数字发生了改变,说明页面没有进行刷新,而是进行了js模块的更新替换
html 文件: 默认不能使用 HMR 功能.同时会导致问题:html 文件不能热更新了~ (不用做 HMR 功能)
解决:修改 entry 入口,将 html 文件引入
module.exports = {
entry: ['./src/index.js','./src/index.html']
}
关于vue / react 等框架的HMR,官方给出了如下解决方案
社区还有许多其他 loader 和示例,可以使 HMR 与各种框架和库(library)平滑地进行交互……
- React Hot Loader:实时调整 react 组件。
- Vue Loader:此 loader 支持用于 vue 组件的 HMR,提供开箱即用体验。
- Elm Hot Loader:支持用于 Elm 程序语言的 HMR。
- Redux HMR:无需 loader 或插件!只需对 main store 文件进行简单的修改。
- Angular HMR:没有必要使用 loader!只需对主要的 NgModule 文件进行简单的修改,由 HMR API 完全控制。
JS兼容性处理(Babel)
官方网站:https://babeljs.io/
Babel是javaScript编译器,能将ES6代码转换成ES5代码,让我们开发过程中可以使用js新特性而不用担心兼容性的问题,还可以通过插件的机制根据需求灵活扩展
Babel在执行编译的过程中,会从项目下的
.babelrc
JSON文件中读取配置,没有该文件会从loader的options读取配置
babel 处理过程 -> 分析依赖 -> AST(抽象语法树) -> 通过语法转换规则转换代码 -> 生成代码
npm install --save-dev babel-loader @babel/core @babel/preset-env
1.babel-loader是webpack 与 babel的通信桥梁,不做把es6转成 es5的工作,这部分工作需要用到@babel/preset-env来做
2.@babel/preset-env里里包含了es,6,7,8转es5的转换规则
默认配置
// webpack.config.js
{
test: /\.js$/i,
exclude: /node_modules/i,
loader: 'babel-loader',
options: {
// todo something..
},
},
这样做不会对高级的语法进行转换。
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
]
}
通过上面的几步还不够,默认的Babel只支持let等一些基础的特性转换,Promise等还没有转换过来,这时候需要借助@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性
npm install --save @babel/polyfill
兼容性在 IE9+
index.js 顶部
import '@babel/polyfill'
按需加载,减少冗余
兼容性按需加载 core-js
npm i core-js -D
会发现打包的体积大了很多,这是因为polyfill默认会把所有特性注入进来,假如我想我用到的es6+,才会注入,没用到的不注入,从而减少打包的体积,可不可以呢
当然可以
修改webpack.config.js
options: {
presets: [
[
"@babel/preset-env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1"
},
corejs: 2,//新版本需要指定核⼼库版本
useBuiltIns: "usage"//按需注入 不用在入口文件import
}
]
]
}
useBuiltIns 选项是 babel 7 的新功能,这个选项告诉 babel 如何配置
@babel/polyfill。 它有三个参数可以使用: ①entry: 需要在 webpack 的入口文件
import “@babel/polyfill"一次。 babel会根据你的使用情况导入垫片,没有使用的功能不会被导入相应的垫片。②usage: 不需要
import,全自动检测,但是要安装
@babel/polyfill。(试验阶段) ③false: 如果你
import”@babel/polyfill",它不会排除掉没有使用的垫片,程序体积会庞大。(不推荐)
扩展:
babelrc文件:新建.babelrc文件,把options的部分转移到该文件中,就可以了
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
},
"useBuiltIns": "usage",
"corejs": 2 //新版本需要指定核⼼库版本
}
]
]
}
// webpack.config.js
{
test: /\.js$/,
//! 排除
exclude: /node_modules/,
loader: 'babel-loader'
}
以上的配置在写组件库或类库代码时失效,因为@babel/polyfill是全局注入语法的,污染了全局环境
react打包环境
安装依赖
npm install react react-dom -S
npm install @babel/preset-react -D
// 在入口文件'./src/index.js'编写react代码
import React, { Component } from 'react';
import ReactDom from 'react-dom'
class App extends Component {
render () {
return (
<div>
hello world
</div>
);
}
}
ReactDom.render(<App></App>, document.querySelector('#root'))
多页面打包通用方案
entry:{
index:"./src/index",
list:"./src/list",
detail:"./src/detail"
},
output: {
filename: '[name].js'
},
new htmlWebpackPlugins({
title: "index.html",
template: path.join(__dirname, "./src/index/index.html"),
filename:"index.html",
chunks:[index]
})
-
目录结构调整
- src
- index
- index.js
- index.html
- list
- index.js
- index.html
- title
- index.js
- index.html
- index
- src
-
使用 glob.symc 第三方库来匹配路径
npm install glob html-webpack-plugin -D
// webpack.config.js
// MPA 多页面打包通用方案
const path = require('path')
const HtmlWebpackPlugin =require('html-webpack-plugin')
const glob = require('glob')
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname, "./src/*/index.js"));
entryFiles.map((item, index) => {
const entryFile = entryFiles[index];
const match = entryFile.match(/src\/(.*)\/index\.js$/);
const pageName = match && match[1];
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
title: pageName,
template: path.join(__dirname,
`src/${pageName}/index.html`),
filename: `${pageName}.html`,
chunks: [pageName],
inject: true
})
);
});
return {
entry,
htmlWebpackPlugins
};
};
const { entry, htmlWebpackPlugins } = setMPA()
module.exports = {
entry,
output:{
path: path.resolve(__dirname, "./build"),
// filename: "[name].js"
filename: '[name]_[chunkhash:8].js'
}
plugins: [
// ...
...htmlWebpackPlugins//展开数组
],
mode: 'development'
}