webpack 笔记(三)

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 解决业务代码和库引用同一个模块的重复加载问题

https://webpack.js.org/configuration/externals/

  • 利用Externals
module.eports = {
    mode: 'production',
    entry: './src/index.js',
    //库中不打包lodash代码
    external: 'lodash'
    ...
}

2.1 编写一个Plugin

https://webpack.js.org/api/compiler-hooks/

  • 在自定义插件的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

官方网站:https://eslint.org/docs/user-guide/configuring

  • 通常使用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性能优化

  1. 更新Node,Npm,Yarn
  2. 在尽可能少的模块上应用Loader
  3. Plugin尽可能精简并确保可靠
  4. 使用DLLPlugin提高打包速度
  5. 控制打包文件的大小
  6. thread-loader, parallel-webpack, happypack多进程打包
  7. 合理使用sourceMap
  8. 结合stats分析打包结果
  9. 开发环境内存编译(webapck-dev-server)
  10. 开发环境无用插件剔除

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,让第三方模块只在第一次打包时生效。

  1. 解决第三方模块只打包一次问题
  • webpack.dll.js是用来将第三方模块的代码进行打包,并用library(库打包)引用该库将使某一自定义变量暴露为全局变量。
  • 通过插件add-asset-html-webpack-plugin使得webpack.common.js配置文件能加载到打包后的静态资源文件。
  1. 用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": {
  }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值