一、Webpack 的构建流程主要有哪些环节?如果可以请尽可能详尽的描述 Webpack 打包的整个过程
1、初始化项目,初始化参数:根据用户配置的不同参数 2、初始化参数,根据用户在命令窗输入的参数,以及 webpack.config.js 的配置,得到最后的配置 3、开始编译,根据上一步得到的最后的配置参数,初始化得到一个 compiler 对象,注册所有的插件 plugin ,插件开始监听 webpack 的构建过 程, 不同的环节会对应相应的处理然后开始执行编译。 3、确定入口,根据 webpack.config.js 中的 entry 入口,开始解析文件构建 AST 语法树,找出依赖递归下去。 4、编译模块,在递归过程中,根据文件类型和 loader 配置,调用相应的 loader 对不同的文件进行不同的转换处理。再找出该模块依赖的模块, 直到项目中依赖的所有模块都经历过编译处理 5、完成编译并输出、递归结束后,得到每个文件结果,包含转换后的模块及他们之间的依赖关系,根据 entry 以及 output 等配置生成代码块, 会在结束之前清楚 dist 目录。 6、打包完成、根据 output 输出到指定目录。
2、Loader 和 Plugin 有哪些不同?请描述一下开发 Loader 和 Plugin 的思路。
一、Loader 直译为 “加载器”,主要是用来解析和检测对应资源,负责源文件从输入到输出的转换,他专注于实现资源模块的加载。 将一些浏览器不支持或者兼容有问题的代码,处理为浏览器可以支持的资源。如:将 ES6+ 转换为浏览器可以识别的 ES5、Sass 转换为 css。 1、通过 moudule.exports 导出一个函数 2、该函数默认参数 第一个参数 source (即要处理的资源文件) 3、在函数体中处理资源(Loder 里配置响应的 Loder 后) 4、通过 return 返回最后打包的结果(这里返回的结果需要为字符串格式)
二、Plugin 主要在 webpack 构建的不同阶段执行一些额外的工作,比如拷贝静态资源、清空打包后的文件夹等。 1、通过钩子机制实现 2、插件必须是一个函数,或者包含 apply 方法的对象 3、在方法体内通过 webpack 提供的 API 获取资源做响应处理 4、将处理完的资源通过 webpack 提供的方法返回给该资源
## webpack.common.js
const resolveApp = require('./paths')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const TerserPlugin = require("terser-webpack-plugin")
const { merge } = require('webpack-merge')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin")
const smp = new SpeedMeasurePlugin()
const prodConfig = require('./webpack.dev')
const devConfig = require('./webpack.dev')
const commonConfig = {
entry: './src/main.js',
devtool: 'source-map',
resolve: {
extensions: [".js", ".json", ".ts", ".jsx", ".vue"],
alias: {
'@': resolveApp('./src')
}
},
output: {
filename: 'js/build.js',
path: resolveApp('./dist'),
publicPath: '/lg'
},
externals: {
lodash: '_'
},
optimization: {
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 20000,
minChunks: 1,
cacheGroups: {
syVendors: {
test: /[\\/]node_modules[\\/]/,
filename: 'js/[id]_vendor.js',
priority: -10,
},
default: {
minChunks: 2,
filename: 'js/syy_[id].js',
priority: -20,
}
}
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer'),
require('postcss-preset-env')
]
}
}
},
{
test: /\.(png|svg|gif|jpe?g)$/,
type: 'asset',
generator: {
filename: 'img/[name].[hash:6].[ext]'
},
parser: {
dataUrlCondition: {
maxSize: 30 * 1024
}
}
},
{
test: /\.(ttf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[hash:3][ext]'
}
}
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.js$/,
use: ['bable-loader']
},
{
test: /\.jsx?$/,
use: ['babel-loader']
},
{
test: /\.vue$/,
use: ['vue-loader']
},
{
test: /\.ts$/,
use: ['bable-loader']
},
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'html-webpack-plugin',
template: './public/index.html'
})
]
}
module.exports = (env) => {
const isProduction = env.Production
process.env.NODE_ENV = isProduction ? 'prodConfig' : 'development'
const config = isProduction ? prodConfig : devConfig
const mergeConfig = merge(commonConfig, config)
return mergeConfig
return smp.wrap(mergeConfig)
}
## webpack.dev.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
target: 'web',
devServer: {
hot: true,
publicPath: '/lg',
contentBase: path.resolve(__dirname, 'public'),
watchContentBase: true,
hotOnly: true,
port: 4000,
open: false,
compress: true,
historyApiFallback: true,
proxy: {
'/api': {
target: 'https://api.github.com',
pathRewrite: { "^/api": "" },
changeOrigin: true
}
}
},
plugins: [
new VueLoaderPlugin()
]
}
## 标题webpack.prod
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require("terser-webpack-plugin")
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const resolveApp = require('./paths')
const glob = require('glob')
const CompressionPlugin = require("compression-webpack-plugin")
const InlineChunkHtmlPlugin = require('inline-chunk-html-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'production',
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin({
extractComments: false
})
]
},
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: 'public',
globOptions: {
ignore: ['**/index.html']
}
}
]
}),
new PurgeCSSPlugin({
paths: glob.sync(`${resolveApp('./src')}/**/*`, { nodir: true }),
safelist: function () {
return {
standard: ['body', 'html', 'ef']
}
}
}),
new CompressionPlugin({
test: /\.(css|js)$/,
minRatio: 0.8,
threshold: 0,
algorithm: 'gzip'
}),
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime.*\.js/])
]
}
## paths.js
const path = require('path')
const appDir = process.cwd()
console.log(appDir, '<---')
const resolveApp = (relativePath) => {
return path.resolve(appDir, relativePath)
}
module.exports = resolveApp
## babel.config.js
const presets = [
[
'@babel/preset-env',
{
useBuiltIns: 'entry',
corejs: 3
}
],
[ '@babel/preset-react' ]
]
const plugins = []
console.log(process.env.NODE_ENV, '<-----')
const isProduction = process.env.NODE_ENV === 'production'
if (!isProduction) {
plugins.push(['react-refresh/babel'])
}
module.exports = {
presets,
plugins
}
## postcss.config.js
module.exports = {
plugins: [
require('postcss-preset-env')
]
}
## Server.js
const express = require('express')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpack = require('webpack')
const app = express()
const config = require('./webpack.common.js')
const compiler = webpack(config)
app.use(webpackDevMiddleware(compiler))
app.listen(3000, () => {
console.log('服务运行在 3000端口上')
})
## .browserslistr
> 1%
last 2 version
not dead
## babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
userBuilIns: 'usage',
corejs: 3
}
],
['@vue/cli-plugin-babel/preset']
]
}
## package.js
{
"name": "vue-app-base",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "webpack serve --config ./config/webpack.common.js --env development",
"build": "webpack --config ./config/webpack.common.js --env production",
"ck": "tsc --noEmit",
"lint": "npm run serve"
},
"sideEffects": [
"./src/title.js"
],
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11"
},
"devDependencies": {},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
"description": "1. 这是一个使用 Vue CLI 创建出来的 Vue 项目基础结构\r 2. 有所不同的是这里我移除掉了 vue-cli-service(包含 webpack 等工具的黑盒工具)\r 3. 这里的要求就是直接使用 webpack 以及你所了解的周边工具、Loader、Plugin 还原这个项目的打包任务\r 4. 尽可能的使用上所有你了解到的功能和特性",
"main": "babel.config.js",
"keywords": [],
"author": "",
"license": "ISC"
}
## .eslintrc.js
const commonConfig = require('./webpack.common')
const merge = require('webpack-merge')
const path = require('path')
module.exports = merge(commonConfig,{
mode:'development',
devtool:'cheap-module-eval-source-map',
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
use: 'eslint-loader',
enforce:'pre'
},
]
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 9000,
hot: true
}
})
|