webpack 笔记(三)
1 Library(库)打包
1.1 解决导出库的引用问题
- 创建了一个library的库,要使别人使用时能采用多用方式引入,配置如下:
引用方式:
import library from 'library'
const library = require('library')
require(['library'], function(){
})
<script src="library.js"></script>
webpack.config.js:
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js'
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
//主要是这句!!!!!
libraryTaget: 'umd',
//保证script标签能引入,在全局变量中添加了library这个变量
library: 'library'
}
}
1.2 解决业务代码和库引用同一个模块的重复加载问题
- 利用Externals
module.eports = {
mode: 'production',
entry: './src/index.js',
//库中不打包lodash代码
external: 'lodash'
...
}
2.1 编写一个Plugin
- 在自定义插件的js文件中定义一个类,该类会打印出传过来的选项参数,并生成一个copyright.txt的文件
class CopyrightWebpackPlugin{
constructor(options) {
//参数是通过webpack配置文件
//new一个实例后传过来的
console.log(options)
}
//compiler是一个webpack的实例
apply(compiler) {
//利用 compile(同步,用tap)
compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => {
console.log('编译')
})
//利用钩子emit(打包完成时,异步tapAsync )
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => {
//增加断电,让node可以调试
debugger;
compilation.assets['copyright.txt'] = {
source: function() {
return 'copyright by Tsai'
},
size: function() {
return 21
}
};
cb();
})
}
}
module.exports = CopyrightWebpackPlugin
- 在webpack配置中引入该插件并在plugins中new一个实例。
- 用node进行debug
package.json:
script: {
"debug": "node --inspect --inspect-brk node_modules/webpack/bin/webpack.js",
}
- 在浏览器控制台可以进入node调试页面
在插件中添加debugger增加断点。目的是可以看到各个参数对象中有什么内容,调试插件。
3.1 TypeScript打包配置
- TypeScript是JS类型的超集,可以编译成纯JS代码。好处:类型校验
例子:
//创建一个类
class Greeter {
//定义一个greeting的字符串变量
greeting: string;
//内置一个构造函数,传入Greeter的参数(必须是字符串)将赋值给greeting
constructor(message: string) {
this.greeting = message;
}
//内置一个greet方法
//(本质就是定义一个构造函数的原型方法)
greet() {
return "Hello, " + this.greeting;
}
}
//实例化并传参进行初始化赋值
let greeter = new Greeter("world");
let button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function() {
alert(greeter.greet());
}
document.body.appendChild(button);
转化为JS语言:
var Greeter = /** @class */ (function () {
//构造函数模式
function Greeter(message) {
this.greeting = message;
}
//原型模式
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
}());
var greeter = new Greeter("world");
var button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function () {
alert(greeter.greet());
};
document.body.appendChild(button);
- webpack中需要用ts-loader来进行打包。要在根目录添加tsconfig.json进行配置。
安装:
npm install ts-loader typescript -D
配置:
webpack.config.js:
module.exports = {
entry: './src/index.tsx'
....
module: {
rules: [{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}]
}
}
tsconfig.json:
{
"compilerOptions": {
//打包输出位置
"outDir": "./dist",
//模块中使用的语法
"module": "es6",
//最终转换的语法
"target": "es5",
//允许使用JS模块
"allowJs": true
}
}
3.2 TS引用其他库所需安装的包
查询有无对应的包:https://microsoft.github.io/TypeSearch/
github仓库:https://github.com/DefinitelyTyped/DefinitelyTyped
- @types/lodash安装该模块后,在tsx文件中引用lodash,如果方法没使用正确,会提示错误。jquery同理。
安装:
npm install @types/lodash --D
引用规范:
import * as _ from 'lodash'
4 EsLint
- 通常使用VSCode的EsLint就可以了,主要是配置文件。
安装:
npm install eslint --save
//解析器安装:
npm install babel-eslint --save-dev
配置:
npx eslint --init
选择:
1. use a popular style guide
2. Airbnb(比较严格)
3. yes
4. JavaScript
5. Y
生成(.eslintrc.js):
module.exports = {
//本身自带的
"extends": "airbnb"
//配置:
//解析器
"parser": "babel-eslint"
}
监测:
npx eslint src(在命令行中显示错误,很麻烦)
- 有些错误想规避掉,可以将鼠标移至错误处,复制eslint(括号中的内容),将其写入.eslintrc.js中rules并赋值为0即可。
例子:
module exports = {
"extends": "airbnb",
"parser": "babel-eslint",
"rules": {
"react/prefer-sxx": 0
},
//让全局变量可以直接使用且不允许被覆盖
globals: {
document: false
}
}
4.1 在webpcak中的配置
- 利用eslint-loader。
按照:
npm install eslint-loader --save-dev
配置:
webpack.config.js:
module.exports = {
mode:xxx
...
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: [
'balbel-loader', {
loader: 'eslint-loader'
options: {
fix: true
}
}]
}]
},
devServer: {
//将错误显示在页面中
overlay: true
}
}
package.json:
script: {
"start": "webpack-dev-server"
}
5 webpack性能优化
- 更新Node,Npm,Yarn
- 在尽可能少的模块上应用Loader
- Plugin尽可能精简并确保可靠
- 使用DLLPlugin提高打包速度
- 控制打包文件的大小
- thread-loader, parallel-webpack, happypack多进程打包
- 合理使用sourceMap
- 结合stats分析打包结果
- 开发环境内存编译(webapck-dev-server)
- 开发环境无用插件剔除
5.1 优化后的1.0版本
根据前三点得到的优化
- 优化后的webpack-dev-config:
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const devConfig = {
//配置模式,默认是production(会压缩js),可以是development(就不会压缩)
mode: 'development',
// sourceMap能告诉我们打包前哪个文件有误
devtool: 'cheap-module-eval-source-map',
// 服务器(指定好放置位置)
devServer: {
//代理:当访问api时会自动转到端口号3000的服务器上
proxy: {
'/api': 'http://localhost:3000'
},
contentBase: path.join(__dirname, 'dist'),
//自动帮你打开浏览器和主页
open: false,
port: 8080,
//让页面缓存(服务器重启时保留 动态增加的元素)
hot: true
},
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoader: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
//热模块替换(页面无须刷新即可响应改变)
new webpack.HotModuleReplacementPlugin()
],
//配置Tree Shaking(只有development开发环境下才要配置)
optimization: {
usedExports: true
}
}
module.exports = merge(commonConfig, devConfig)
- 优化后的webpack.common.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
//include: path.resolve(__dirname, '../src')
use: [{
loader: 'babel-loader'
}]
}, {
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}, {
test: /\.(css|sass)$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
'postcss-loader'
]
}, {
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'vue-loader'
}
}]
},
//插件
plugins: [
//自动生成index.html并引入用到的文件
new HtmlWebpackPlugin({
template: './src/index.html'
}),
//打包前清空dist文件
new CleanWebpackPlugin({
root: path.resolve(__dirname, '../')
})
],
//优化配置
optimization: {
//运行时用到的代码放置在runtime中
runtimeChunk: {
name: 'runtime'
},
//通过package.json中sideEffects设置css文件不做TreeShaking
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors'
}
}
}
},
//不显示打包过程中的性能问题
performance: false,
//output表输出
output: {
//表示打包后的js文件名
filename: '[name].js',
//表示输出路径,要加绝对路径,所以引用path模块
path: path.resolve(__dirname, '../dist')
}
}
- 优化后的webpack.prod.js:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const devConfig = {
mode: 'production',
// sourceMap能告诉我们打包前哪个文件有误
devtool: 'cheap-module-source-map',
module: {
rules: [{
test: /\.scss$/,
use: [
//把css独立抽象出来,打包成独立文件(替代style-loader)
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css?$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
},
optimization: {
//对css进行压缩
minimizer: [new OptimizeCssAssetsPlugin({})]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'
})
],
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
}
}
module.exports = merge(devConfig, commonConfig)
- .babelrc.js
日常开发:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "58",
"ie": "6"
},
"useBuiltIns": "usage",
"corejs": {
"version": 3,
"proposals": true
}
}
]
],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
内建库时:
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 3,
"helpers": true,
"regenerator": true,
"useESModules": false
}
],
["dynamic-import-webpack"]
],
}
5.2 优化后的2.0版本
创建webpack.dll.js,让第三方模块只在第一次打包时生效。
- 解决第三方模块只打包一次问题:
- webpack.dll.js是用来将第三方模块的代码进行打包,并用library(库打包)引用该库将使某一自定义变量暴露为全局变量。
- 通过插件add-asset-html-webpack-plugin使得webpack.common.js配置文件能加载到打包后的静态资源文件。
- 用dll文件引入第三方模块
- 借助DllPlugin这个插件使其分析并生成第三方模块对应映射关系的JSON文件。在webpack.common.js借助配套的webpack.DllReferencePlugin。目的是使配置文件会优先使用已打包的第三方模块文件,而不必去node_module中找。
- 第三方模块打包文件(js)和映射关系(json)文件将其放置到dll的独立文件夹。
webpack.dll.js:
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
//引用的第三方模块
vendors: ['react', 'react-dom', 'lodash']
},
plugins: [
//把第三方模块映射关系放到[name].manifest.json文件上
new webpack.DllPlugin({
name: 'name',
path: path.resolve(__dirname, '../dll/[name].manifest.json')
})
],
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
//利用全局变量的形式暴露出去
library: '[name]'
}
}
- 利用增加静态资源插件(AddAssetHtmlWebpackPlugin)引入第三方模块打包生成的js文件。
安装依赖包:
add-asset-html-webpack-plugin --save
(增加静态资源)
配置:
webpack.common.js
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
const commonConfig = {
...
plugins: [
new AddAssetHtmlWebpackPlugin({
filepath: '../dll/vendors.dll.js'
})
]
}
- 当引用过多第三方模块时,若通过dll分割成多个js文件,则可以通过NodeJS的js模块可以获取dll文件夹下边的文件名,在webpack.common.js中通过forEach便可遍历增加第三方模块静态资源和其映射关系。
webpack.common.js:
const fs = require('fs');
//将插件单独写
const plugins = [
//自动生成index.html并引入用到的文件
new HtmlWebpackPlugin({
template: './src/index.html'
}),
//打包前清空dist文件
new CleanWebpackPlugin({
root: path.resolve(__dirname, '../')
})
];
//为了让第三方模块打包后自动加入静态资源JS文件和映射关系的JSON文件
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
//循环遍历dll文件夹
files.forEach(file => {
if(/.*\.dll.js/.test(file)) {
plugins.push(new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if(/.*\.manifest.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
});
module.exports = {
entry: ...
...
plugins,//键值一致可省略
output: ...
}
5.3 最终代码(单页面)
- webpack.dev.js:
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const devConfig = {
//配置模式,默认是production(会压缩js),可以是development(就不会压缩)
mode: 'development',
// sourceMap能告诉我们打包前哪个文件有误
devtool: 'cheap-module-eval-source-map',
// 服务器(指定好放置位置)
devServer: {
//代理:当访问api时会自动转到端口号3000的服务器上
proxy: {
'/api': 'http://localhost:3000'
},
contentBase: path.join(__dirname, 'dist'),
//自动帮你打开浏览器和主页
open: false,
port: 8080,
//让页面缓存(服务器重启时保留 动态增加的元素)
hot: true
},
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoader: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
//热模块替换(页面无须刷新即可响应改变)
new webpack.HotModuleReplacementPlugin()
],
//配置Tree Shaking(只有development开发环境下才要配置)
optimization: {
usedExports: true
}
}
module.exports = merge(commonConfig, devConfig)
- webpack.common.js:
const path = require('path');
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
//将插件单独写
const plugins = [
//自动生成index.html并引入用到的文件
new HtmlWebpackPlugin({
template: './src/index.html'
}),
//打包前清空dist文件
new CleanWebpackPlugin({
root: path.resolve(__dirname, '../')
})
];
//为了让第三方模块打包后自动加入静态资源JS文件和映射关系的JSON文件
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
if(/.*\.dll.js/.test(file)) {
plugins.push(new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if(/.*\.manifest.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
});
module.exports = {
entry: {
main: './src/index.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
//include: path.resolve(__dirname, '../src')
use: [{
loader: 'babel-loader'
}]
}, {
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}, {
test: /\.(css|sass)$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
'postcss-loader'
]
}, {
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'vue-loader'
}
}]
},
//插件
plugins,
//优化配置
optimization: {
//运行时用到的代码放置在runtime中
runtimeChunk: {
name: 'runtime'
},
//通过package.json中sideEffects设置css文件不做TreeShaking
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors'
}
}
}
},
//不显示打包过程中的性能问题
performance: false,
//output表输出
output: {
//表示打包后的js文件名
filename: '[name].js',
//表示输出路径,要加绝对路径,所以引用path模块
path: path.resolve(__dirname, '../dist')
}
}
- webpack.prod.js:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const devConfig = {
mode: 'production',
// sourceMap能告诉我们打包前哪个文件有误
devtool: 'cheap-module-source-map',
module: {
rules: [{
test: /\.scss$/,
use: [
//把css独立抽象出来,打包成独立文件(替代style-loader)
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css?$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
},
optimization: {
//对css进行压缩
minimizer: [new OptimizeCssAssetsWebpackPlugin({})]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'
})
],
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
}
}
module.exports = merge(devConfig, commonConfig)
- webpack.dll.js:
const path = require('path');
const webpack = require('webpack')
module.exports = {
mode: 'production',
entry: {
//引用的第三方模块
jqueryAndVue: ['jquery', 'vue'],
},
plugins: [
//把第三方模块映射关系放到[name].manifest.json文件上
new webpack.DllPlugin({
name: 'name',
path: path.resolve(__dirname, '../dll/[name].manifest.json')
})
],
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
//利用全局变量的形式暴露出去
library: '[name]'
}
}
- package.json:
{
"name": "type-script",
"sideEffects": [
"*.css"
],
"version": "1.0.0",
"description": "",
"scripts": {
"dev-build": "webpack --config ./build/webpack.dev.js",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js",
"build:dll": "webpack --config ./build/webpack.dll.js"
},
"author": "Zeg",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5",
"@babel/runtime-corejs3": "^7.5.5",
"add-asset-html-webpack-plugin": "^3.1.3",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^3.1.4",
"css-loader": "^3.1.0",
"file-loader": "^4.1.0",
"html-webpack-plugin": "^3.2.0",
"jquery": "^3.4.1",
"lodash": "^4.17.15",
"mini-css-extract-plugin": "^0.8.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"ts-loader": "^6.0.4",
"typescript": "^3.5.3",
"url-loader": "^2.1.0",
"vue": "^2.6.10",
"vue-loader": "^15.7.1",
"webpack": "^4.37.0",
"webpack-cli": "^3.3.6",
"webpack-dev-middleware": "^3.7.0",
"webpack-dev-server": "^3.7.2",
"webpack-merge": "^4.2.1"
},
"dependencies": {
}
}