工程:webpack

156 阅读1分钟

官方文档

webpck是什么?

1.模块打包工具bundler 模块是什么? // ES Module // common.js // CMD // AMD

ES Module
1.import Header from './header.js';
2.export default Header
common.js
1.var Header = require(./header);
2.module.exports = Header;

2.js 模块打包工具 -> css\html\jpg 等 3.底层基于node

常用指令

webpack --config [入口文件]

核心概念

loader

定义:打包方案,对于特定文件怎么去打包

module: {
	rules: [{
		test: /\.(jpg|png|gif)$/,
		use: {
			loader: 'url-loader',
			options: {
				name: '[name]_[hash].[ext]', // name、hash 为placeholder占位符
				outputPath: 'images/',
				limit: 10240
			}
		} 
	}]
},

常用loader

  • file-loader
  • url-loader(默认把文件转化成base64, 直接加载到js里。使用于小文件,limit)
  • vue-loader
  • css-loader 分析css文件依赖关系,合并css文件
  • style-loader 把css内容挂载到head
  • sass-loader loader顺序:从下到上,从右到左
module: {
    rules: [{
        test: /\.(jpg|png|gif)$/,
        use: {
            loader: 'url-loader',
            options: {
                name: '[name]_[hash].[ext]',
                outputPath: 'images/',
                limit: 10240
            }
        }
    }, {
        test: /\.scss$/,
        use: [
            'style-loader',
            {
                loader: 'css-loader',
                options: {
                    importLoaders: 2, // 重新执行前N个loader
                    modules: true // css-module 模块化
                }
            },
            'sass-loader',
            'postcss-loader'
        ]
    }]
},

plugin

plugin 可以在打包的节点上让webpack做一些事情 插件是什么? 例子,HtmlWebpackPlugin:会在打包结束后自动生成html文件,并且把打包生成的文件自动引入到html文件中

plugins: [new HtmlWebpackPlugin({
	template: 'src/index.html'
}), new CleanWebpackPlugin(['dist'])],

常用插件

  • HtmlWebpackPlugin
  • CleanWebpackPlugin 清除插件
多个文件打包
	entry: {
		main: './src/index.js',
		sub: './src/index.js'
	},
	output: {
		publicPath: 'http://cdn.com.cn', // public Path ,最终在引入时会自动添加到路径前
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	} // 多个文件时HtmlWebpackPlugin会自动同时引入

详细参数见文档output output management

sourceMap

是什么? 映射关系:打包后的文件和source里文件位置的映射,打包出错时的定位

	// development devtool: 'cheap-module-eval-source-map',
	// production devtool: 'cheap-module-source-map',
	devtool: 'cheap-module-eval-source-map'/'source-map',

参考文档

  • cheap:只显示行不显示列
  • module: 是否cover除业务代码外模块
  • inline:case64
  • eval: eval执行方式来执行
webpack devServer

功能:热更新&打开浏览器&开启一个代码服务器 打包后放在内存里 自己手写一个devServer

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
// 在node中直接使用webpack
// 在命令行里使用webpack
const complier = webpack(config);
// 生成一个webpack编译器
const app = express();
// webpackDevMiddleware 中间件,监听文件变化
app.use(webpackDevMiddleware(complier, {}));

app.listen(3000, () => {
	console.log('server is running');
});

command line官方文档

HMR 热模块替换
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist']),
		new webpack.HotModuleReplacementPlugin()
	],
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true
	},

高级概念

tree shaking

摇树🌲,把没用的东西剔除掉 只支持ES Module 开发环境下保留代码,上线会删除

optimization: {
    usedExports: true
}
package.json
sideEffects: ['*.css'], // 不执行tree shaking的模块
lazy loading

对一些模块进行懒加载,通过import 异步加载

async function getComponent() {
	const { default: _ } = await import(/* webpackChunkName:"lodash" */ 'lodash');
	const element = document.createElement('div');
	element.innerHTML = _.join(['Dell', 'Lee'], '-');
	return element;
}

document.addEventListener('click', () =>{
	getComponent().then(element => {
		document.body.appendChild(element);
	});
})
webpack 环境变量使用
  "scripts": {
    "dev-build": "webpack --config ./build/webpack.common.js",
    "dev": "webpack-dev-server --config ./build/webpack.common.js",
    "build": "webpack --env.production --config ./build/webpack.common.js" // env,往配置文件传入production,默认true
  },

webpack 实践

打包性能优化
  • 更新工具版本(webpack、node、yarn、npm)
  • 在尽可能少的模块上应用loader
  • 尽可能少使用plugin ,并使用可靠的plugin
{ 
			test: /\.js$/, 
			include: path.resolve(__dirname, '../src'), // include 和exclude的使用
			use: [{
				loader: 'babel-loader'
			}]
		}
  • resolve 参数合理配置,resolve 会调用底层查找逻辑,成本较大
	resolve: {
		extensions: ['.js', '.jsx'],
		alias: {
			child: path.resolve(__dirname, '../src/a/b/c/child')
		}
	},
  • 使用一些提高打包速度的插件(引入第三方模块时,第一次引入分析,再次打包不分析第三方模块)
module.exports = {
	mode: 'production',
	entry: {
		vendors: ['lodash'],
		react: ['react', 'react-dom'],
		jquery: ['jquery']
	},
	output: {
		filename: '[name].dll.js',
		path: path.resolve(__dirname, '../dll'),
		library: '[name]' // 打包后的文件通过全局变量暴露出去
	},
	plugins: [
		new webpack.DllPlugin({
			name: '[name]',
			path: path.resolve(__dirname, '../dll/[name].manifest.json'),
		})
	]
}
配合配置
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
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)
		}))
	}
})
  • 控制包文件大小
  • 多进程打包thread-loader、parellel-webpack
  • 合理使用sourceMap
  • 开发环境内存编译(webpack-dev-server)
  • 开发环境无用插件剔除

底层原理

如何手写一个loader

loader 官方文档

// replaceLoader.js
const loaderUtils = require('loader-utils'); // 官方推荐获取options 传入参数的方法

module.exports = function(source) {
	const options = loaderUtils.getOptions(this);
	const callback = this.async();

	setTimeout(() => {
		const result = source.replace('dell', options.name);
		callback(null, result);
	}, 1000);
}
// webpack.config.js
const path = require('path');

module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js'
	},
	resolveLoader: {
		modules: ['node_modules', './loaders']
	},
	module: {
		rules: [{
			test: /\.js/,
			use: [
				{
					loader: 'replaceLoader',
				},
				{
					loader: 'replaceLoaderAsync',
					options: {
						name: 'lee'
					}
				},
			]
		}]
	},
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: '[name].js'
	}
}
如何手写一个plugin