открытие
Многие люди более или менее использовали webpack, но мало кто может систематически изучать настройку webpack, так как при столкновении с ошибками они будут растеряны и не будут знать, с чего начать? Я не знаю, что делать при оптимизации производительности.Подойдет ли онлайн-учебник по оптимизации для моего собственного проекта? Жду серию вопросов! Эта статья представляет собой пошаговый процесс от самой базовой конфигурации до полного крупномасштабного проекта. Пусть вы больше не боитесь вебпака, пусть он действительно станет вашей правой рукой!
В данной работе реализованы следующие темы:
- Тема 1:Первый взгляд на веб-пакет? Изучите принципы упаковки webpack
- Тема 2:Создавайте среды разработки и производства
- Тема 3:Загрузчик базовой комплектации
- Урок 4:оптимизация производительности веб-пакета
- Урок 5:Рукописный загрузчик реализует необязательную цепочку
- Урок 6:оптимизация компиляции webpack
- Урок 7:Многостраничная конфигурация
- Урок 8:Рукописный плагин веб-пакета
- Урок 9:построить сср
адрес проекта
GitHub.com/falling snow-Вик Т…
Я разделяю каждый урок на разные ветки, каждый может учиться шаг за шагом в соответствии со временем занятий
строительные леса
npm i -g webpack-box
использовать
webpack-box dev # 开发环境
webpack-box build # 生产环境
webpack-box dll # 编译差分包
webpack-box dev index # 指定页面编译(多页面)
webpack-box build index # 指定页面编译(多页面)
webpack-box build index --report # 开启打包分析
webpack-box build:ssr # 编译ssr
webpack-box ssr:server # 在 server 端运行
использовать в package.json
{
"scripts": {
"dev": "webpack-box dev",
"build": "webpack-box build",
"dll": "webpack-box dll",
"build:ssr": "webpack-box build:ssr",
"ssr:server": "webpack-box ssr:server"
}
}
использовать
npm run build --report # 开启打包分析
Расширенная конфигурация
box.config.js
module.exports = function (config) {
/**
* @param {object} dll 开启差分包
* @param {object} pages 多页面配置 通过 box run/build index 来使用
* @param {function} chainWebpack
* @param {string} entry 入口
* @param {string} output 出口
* @param {string} publicPath
* @param {string} port
*/
return {
entry: 'src/main.js',
output: 'dist',
publicPath: '/common/',
port: 8888,
dll: {
venders: ['vue', 'react']
},
pages: {
index: {
entry: 'src/main.js',
template: 'public/index.html',
filename: 'index.html',
},
index2: {
entry: 'src/main.js',
template: 'public/index2.html',
filename: 'index2.html',
}
},
chainWebpack(config) {
}
}
}
Тема 1: Изучение веб-пакета? Изучите принципы упаковки webpack
хочу учитьсяwebpack
, нам сначала нужно понятьwebpack
Механизм, который мы сначала начинаем изучать с js для загрузки css.
Начнем со следующего небольшого упражненияwebpack
Бар
существуетindex.js
введен вindex.css
const css = require('./index.css')
console.log(css)
Файлы CSS не распознаются js, и webpack не является исключением.Приведенный выше метод написания не сообщит об ошибке неожиданно.
Как заставить веб-пакет распознавать css?Ответ заключается в том, что веб-пакет предоставляет нам механизм загрузчика, который позволяет нам преобразовать любой файл в файл, который веб-пакет может распознать через загрузчик.
В этой главе в основном объясняется
- базовая конфигурация вебпака
- Решите, как пакеты загружают модули
- Принцип динамической загрузки импорта
- Переписать конфигурацию с помощью webpack-chain
- Краткое содержание урока 1
базовая конфигурация вебпака
Требуемые зависимости
package.json
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack", // 开发环境
"build": "cross-env NODE_ENV=production webpack" // 生产环境
},
"dependencies": {
"cross-env": "^6.0.3", // 兼容各种环境
"css-loader": "^3.2.0",
"rimraf": "^3.0.0", // 删除文件
"webpack": "^4.41.2"
},
"devDependencies": {
"webpack-cli": "^3.3.10"
}
}
базовая конфигурация вебпака
webpack.config.js
const path = require('path');
const rimraf = require('rimraf');
// 删除 dist 目录
rimraf.sync('dist');
// webpack 配置
module.exports = {
entry: './src/index',
mode: process.env.NODE_ENV,
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
css импортируется в js
src/index.js
const css = require('css-loader!./index.css');
const a = 100;
console.log(a, css);
тест css
src/index.css
body {
width: 100%;
height: 100vh;
background-color: orange;
}
Решите, как пакеты загружают модули
Я удалил некоторые комментарии и некоторые отвлекающие факторы, чтобы все выглядело немного понятнее.
-
bundle
это немедленно выполняемая функция, думайте о ней как об одном гигантском модуле, который связывает все модули вместе. -
webpack
Все модули упакованы вbundle
зависимости, введенные через объект -
0 模块
это вход -
webpack
пройти через__webpack_require__
модуль импорта -
__webpack_require__
это то, что мы используемrequire
,стеганое одеялоwebpack
инкапсулированный
dist/bundle.js
(function(modules) {
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
});
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
module.l = true;
return module.exports;
}
return __webpack_require__((__webpack_require__.s = 0));
})({
'./src/index.js': function(module, exports, __webpack_require__) {
eval(`
const css = __webpack_require__("./src/style/index.css")
const a = 100;
console.log(a, css)
`);
},
'./src/style/index.css': function(module, exports, __webpack_require__) {
eval(`
exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false);
exports.push([module.i, "body {
width: 100%;
height: 100vh;
background-color: orange;
}", ""]);
`);
},
0: function(module, exports, __webpack_require__) {
module.exports = __webpack_require__('./src/index.js');
}
});
Принцип динамической загрузки импорта
Что произойдет, если мы изменим требование index.js на импорт?
мы знаемimport
иrequire
Разница в том,import
Это динамическая загрузка, которая будет загружаться только тогда, когда она используется, иrequire
Он будет загружен, как только он будет объявлен,webpack
Встретилисьrequire
он будет загружен как модуль вbundle
в зависимости
Итак, вопрос в том, если мы используем импорт для ссылки на модуль, как он загружается?
требуется изменить на import()
src/index.js
// const css = require('css-loader!./index.css');
const css = import('css-loader!./index.css');
const a = 100;
console.log(a, css);
Динамически загружать результаты упаковки
кроме нормальногоbundle
Кроме того, мы также можем видеть0.boundle.js
0.boundle.js
наша динамическая загрузкаindex.css
модуль
|-- bundle.js
|-- 0.boundle.js
динамический модуль
0.boundle.js
Этот файл - то, что мыimport
модуль в отдельныйjs
в файле
(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
[0],
{
'./node_modules/css-loader/dist/runtime/api.js': function(
module,
exports,
__webpack_require__
) {
'use strict';
eval(`
...
`);
},
'./src/style/index.css': function(module, exports, __webpack_require__) {
eval(`
exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false));
exports.push([module.i, \`body {
width: 100%;
height: 100vh;
background-color: orange;
},"\`]
`);
}
}
]);
Логика загрузки динамического модуля
Давайте еще раз взглянем на dist/bundle.js.
Для простоты понимания я удалил большую часть кода и комментариев.
Принцип очень простой, то есть использовать принцип реализации jsonp для загрузки модуля, но здесь нужно получать данные не с сервера, а из других модулей.
- При вызове модуля будет
window
зарегистрироваться на одномwebpackJsonp
массив, окно['webpackJsonp'] = окно['webpackJsonp'] || [] - когда мы
import
час,webpack
позвоню__webpack_require__.e(0)
метод, то естьrequireEnsure
-
webpack
будет динамически создаватьscript
тег для загрузки этого модуля, после успешной загрузки модуль будет внедрен вwebpackJsonp
середина -
webpackJsonp.push
позвонюwebpackJsonpCallback
получить модуль - Модуль загружается (затем) и затем используется
__webpack_require__
получить модуль
(function(modules) {
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
var moduleId,
chunkId,
i = 0,
resolves = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (
Object.prototype.hasOwnProperty.call(installedChunks, chunkId) &&
installedChunks[chunkId]
) {
resolves.push(installedChunks[chunkId][0]);
}
// 模块安装完
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if (parentJsonpFunction) parentJsonpFunction(data);
while (resolves.length) {
// 执行所有 promise 的 resolve 函数
resolves.shift()();
}
}
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + '' + ({}[chunkId] || chunkId) + '.bundle.js';
}
function __webpack_require__(moduleId) {
// ...
}
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// ...
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
script.src = jsonpScriptSrc(chunkId);
onScriptComplete = function(event) {
// 处理异常,消除副作用
// ...
};
var timeout = setTimeout(function() {
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script);
// ...
// 动态加载模块
return Promise.all(promises);
};
var jsonpArray = (window['webpackJsonp'] = window['webpackJsonp'] || []);
// 重写数组 push 方法
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for (var i = 0; i < jsonpArray.length; i++)
webpackJsonpCallback(jsonpArray[i]);
return __webpack_require__((__webpack_require__.s = 0));
})({
'./src/index.js': function(module, exports, __webpack_require__) {
eval(`
const css = __webpack_require__.e(0).then(__webpack_require__.t.bind(null, "./src/style/index.css", 7))
const a = 100;
console.log(a, css)
`);
},
0: function(module, exports, __webpack_require__) {
eval(`module.exports = __webpack_require__("./src/index.js");`);
}
});
Переписать конфигурацию с помощью webpack-chain
Мы используем webpack-chain для написания конфигурации webpack, причина в том, что способ webpack-chain более гибкий
официальное объяснение
webpack-chain
попробуйте, предоставив цепочку или нисходящий потокAPI
Создание и изменениеwebpack
конфигурация.API
изKey
На разделы можно ссылаться по заданным пользователем именам, что помогает стандартизировать способы изменения конфигураций в проектах.
const path = require('path');
const rimraf = require('rimraf');
const Config = require('webpack-chain');
const config = new Config();
const resolve = src => {
return path.join(process.cwd(), src);
};
// 删除 dist 目录
rimraf.sync('dist');
config
// 入口
.entry('src/index')
.add(resolve('src/index.js'))
.end()
// 模式
// .mode(process.env.NODE_ENV) 等价下面
.set('mode', process.env.NODE_ENV)
// 出口
.output.path(resolve('dist'))
.filename('[name].bundle.js');
config.module
.rule('css')
.test(/\.css$/)
.use('css')
.loader('css-loader');
module.exports = config.toConfig();
Краткое содержание урока 1
Пока закончился урок 1, мы в основном делали следующие вещи
- базовая конфигурация вебпака
- Упаковать css в js через css-loader
- Анализ того, как пакеты загружают модули
- Как webpack реализует динамическую загрузку модулей
Чтобы изучить инструмент, мы должны не только понять его конфигурацию, но и вместе понять его принципы.Только изучив суть фреймворка, мы можем справиться с бурным развитием большого фронтенда сегодня.
Тема 2: Настройка среды разработки и производственной среды
Краткое содержание этой главы:
- содержание
- Внедрение подключаемых конфигураций
- Создание производственной среды
- Сборка среды разработки (devServer)
- извлечь css
- Автоматически генерировать html
- испытание проекта
содержание
│── build
│ │── base.js // 公共部分
│ │── build.js
│ └── dev.js
│── config
│ │── base.js // 基础配置
│ │── css.js // css 配置
│ │── HtmlWebpackPlugin.js // html 配置
│ └── MiniCssExtractPlugin.js // 提取css
│── public // 公共资源
│ └── index.html // html 模版
└── src // 开发目录
│── style
│ └── index.css
└── main.js // 主入口
Внедрение подключаемых конфигураций
package.json
{
"scripts": {
"dev": "cross-env NODE_ENV=development node build/dev.js",
"build": "cross-env NODE_ENV=production node build/build.js"
},
"dependencies": {
"cross-env": "^6.0.3",
"css-loader": "^3.2.0",
"cssnano": "^4.1.10",
"ora": "^4.0.3",
"rimraf": "^3.0.0",
"webpack": "^4.41.2"
},
"devDependencies": {
"extract-text-webpack-plugin": "^3.0.2",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.8.0",
"vue-cli-plugin-commitlint": "^1.0.4",
"webpack-chain": "^6.0.0",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0"
}
}
build/base.js
const { findSync } = require('../lib');
const Config = require('webpack-chain');
const config = new Config();
const files = findSync('config');
const path = require('path');
const resolve = p => {
return path.join(process.cwd(), p);
};
module.exports = () => {
const map = new Map();
files.map(_ => {
const name = _.split('/')
.pop()
.replace('.js', '');
return map.set(name, require(_)(config, resolve));
});
map.forEach(v => v());
return config;
};
Создание производственной среды
build/build.js
const rimraf = require('rimraf');
const ora = require('ora');
const chalk = require('chalk');
const path = require('path');
// 删除 dist 目录
rimraf.sync(path.join(process.cwd(), 'dist'));
const config = require('./base')();
const webpack = require('webpack');
const spinner = ora('开始构建项目...');
spinner.start();
webpack(config.toConfig(), function(err, stats) {
spinner.stop();
if (err) throw err;
process.stdout.write(
stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n'
);
if (stats.hasErrors()) {
console.log(chalk.red('构建失败\n'));
process.exit(1);
}
console.log(chalk.cyan('build完成\n'));
});
Сборка среды разработки (devServer)
build/dev.js
const config = require('./base')();
const webpack = require('webpack');
const chalk = require('chalk');
const WebpackDevServer = require('webpack-dev-server');
const port = 8080;
const publicPath = '/common/';
config.devServer
.quiet(true)
.hot(true)
.https(false)
.disableHostCheck(true)
.publicPath(publicPath)
.clientLogLevel('none');
const compiler = webpack(config.toConfig());
// 拿到 devServer 参数
const chainDevServer = compiler.options.devServer;
const server = new WebpackDevServer(
compiler,
Object.assign(chainDevServer, {})
);
['SIGINT', 'SIGTERM'].forEach(signal => {
process.on(signal, () => {
server.close(() => {
process.exit(0);
});
});
});
// 监听端口
server.listen(port);
new Promise(() => {
compiler.hooks.done.tap('dev', stats => {
const empty = ' ';
const common = `App running at:
- Local: http://127.0.0.1:${port}${publicPath}\n`;
console.log(chalk.cyan('\n' + empty + common));
});
});
извлечь css
config/css.js
css извлечь конфигурацию загрузчика
module.exports = (config, resolve) => {
return (lang, test) => {
const baseRule = config.module.rule(lang).test(test);
const normalRule = baseRule.oneOf('normal');
applyLoaders(normalRule);
function applyLoaders(rule) {
rule
.use('extract-css-loader')
.loader(require('mini-css-extract-plugin').loader)
.options({
publicPath: './'
});
rule
.use('css-loader')
.loader('css-loader')
.options({});
}
};
};
Плагин извлечения css MiniCssExtractPlugin
config/MiniCssExtractPlugin.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = (config, resolve) => {
return () => {
config
.oneOf('normal')
.plugin('mini-css-extract')
.use(MiniCssExtractPlugin);
};
};
Автоматически генерировать html
config/HtmlWebpackPlugin.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (config, resolve) => {
return () => {
config.plugin('html').use(HtmlWebpackPlugin, [
{
template: 'public/index.html'
}
]);
};
};
испытание проекта
тестовый HTML-шаблон
public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>learn_webpack</title>
<body></body>
</html>
тестовый css-шаблон
src/style/index.css
.test {
width: 200px;
height: 200px;
color: red;
background-color: orange;
}
Запись программы
src/main.js
require('./style/index.css');
const h2 = document.createElement('h2');
h2.className = 'test';
h2.innerText = 'test';
document.body.append(h2);
Тема 3: Загрузчик базовой конфигурации
Краткое содержание этой главы:
- настроить бабел
- Настройте ts с помощью babel
- проверка статического типа ts
- Дружественный плагин сообщений об ошибках
- Настройте стиль, стиль, css, less, sass, postcss и т. д.
- конфигурация postcss
- Сравнение css до и после компиляции
- настроить автопрефиксер
- карта с открытым исходным кодом
содержание
Добавьте следующие файлы
│──── config // 配置目录
│ │── babelLoader.js // babel-loader 配置
│ │── ForkTsChecker.js // ts 静态检查
│ │── FriendlyErrorsWebpackPlugin.js // 友好错误提示
│ └── style
│──── src // 开发目录
│ │── style
│ │ │── app.css
│ │ │── index.less // 测试 less
│ │ │── index.scss // 测试 sass
│ │ └── index.postcss // 测试 postcss
│ └── ts
│ └── index.ts // 测试 ts
│── babel.js
│── postcss.config.js // postcss 配置
│── tsconfig.json // ts 配置
└──── dist // 打包后的目录
│── app.bundle.js
│── app.css
└── index.html
настроить бабел
config/babelLoader.js
module.exports = (config, resolve) => {
const baseRule = config.module.rule('js').test(/.js│.tsx?$/);
const babelPath = resolve('babel.js');
const babelConf = require(babelPath);
const version = require(resolve('node_modules/@babel/core/package.json'))
.version;
return () => {
baseRule
.use('babel')
.loader(require.resolve('babel-loader'))
.options(babelConf({ version }));
};
};
Настройте ts с помощью babel
Здесь мы используемbabel
плагин@babel/preset-typescript
будетts
Превратиться вjs,并使用
ForkTsCheckerWebpackPlugin
,ForkTsCheckerNotifierWebpackPlugin
Плагин отображает сообщение об ошибке.
babel.js
module.exports = function(api) {
return {
presets: [
[
'@babel/preset-env',
{
targets: {
chrome: 59,
edge: 13,
firefox: 50,
safari: 8
}
}
],
[
'@babel/preset-typescript',
{
allExtensions: true
}
]
],
plugins: [
'@babel/plugin-transform-typescript',
'transform-class-properties',
'@babel/proposal-object-rest-spread'
]
};
};
проверка статического типа ts
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ForkTsCheckerNotifierWebpackPlugin = require('fork-ts-checker-notifier-webpack-plugin');
module.exports = (config, resolve) => {
return () => {
config.plugin('ts-fork').use(ForkTsCheckerWebpackPlugin, [
{
// 将async设为false,可以阻止Webpack的emit以等待类型检查器/linter,并向Webpack的编译添加错误。
async: false
}
]);
// 将TypeScript类型检查错误以弹框提示
// 如果fork-ts-checker-webpack-plugin的async为false时可以不用
// 否则建议使用,以方便发现错误
config.plugin('ts-notifier').use(ForkTsCheckerNotifierWebpackPlugin, [
{
title: 'TypeScript',
excludeWarnings: true,
skipSuccessful: true
}
]);
};
};
Дружественный плагин сообщений об ошибках
config/FriendlyErrorsWebpackPlugin.js
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
module.exports = (config, resolve) => {
return () => {
config.plugin('error').use(FriendlyErrorsWebpackPlugin);
};
};
Настройте стиль, стиль, css, less, sass, postcss и т. д.
module.exports = (config, resolve) => {
const createCSSRule = (lang, test, loader, options = {}) => {
const baseRule = config.module.rule(lang).test(test);
const normalRule = baseRule.oneOf('normal');
normalRule
.use('extract-css-loader')
.loader(require('mini-css-extract-plugin').loader)
.options({
hmr: process.env.NODE_ENV === 'development',
publicPath: '/'
});
normalRule
.use('css-loader')
.loader(require.resolve('css-loader'))
.options({});
normalRule.use('postcss-loader').loader(require.resolve('postcss-loader'));
if (loader) {
const rs = require.resolve(loader);
normalRule
.use(loader)
.loader(rs)
.options(options);
}
};
return () => {
createCSSRule('css', /\.css$/, 'css-loader', {});
createCSSRule('less', /\.less$/, 'less-loader', {});
createCSSRule('scss', /\.scss$/, 'sass-loader', {});
createCSSRule('postcss', /\.p(ost)?css$/);
};
};
конфигурация postcss
module.exports = {
plugins: {
'postcss-px-to-viewport': {
unitToConvert: 'px',
viewportWidth: 750,
unitPrecision: 5,
propList: ['*'],
viewportUnit: 'vw',
fontViewportUnit: 'vw',
selectorBlackList: [],
minPixelValue: 1,
mediaQuery: false,
replace: true,
exclude: [],
landscape: false,
landscapeUnit: 'vw',
landscapeWidth: 568
}
}
};
Сравнение css до и после компиляции
src/style/index.less
/* index.less */
.test {
width: 300px;
}
dist/app.css
/* index.css */
.test {
width: 36.66667vw;
height: 26.66667vw;
color: red;
background-color: orange;
}
/* app.css */
.test {
font-size: 8vw;
}
/* index.less */
.test {
width: 40vw;
}
/* index.scss */
.test {
height: 40vw;
}
/* index.postcss */
.test {
background: green;
height: 26.66667vw;
}
настроить автопрефиксер
Автоматически добавлять префикс css
postcss.config.js
module.exports = {
plugins: {
autoprefixer: {
overrideBrowserslist: [
'> 1%',
'last 3 versions',
'iOS >= 8',
'Android >= 4',
'Chrome >= 40'
]
}
}
};
До конвертации
/* index.css */
.test {
width: 200px;
height: 200px;
color: red;
display: flex;
background-color: orange;
}
после преобразования
/* index.css */
.test {
width: 26.66667vw;
height: 26.66667vw;
color: red;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
background-color: orange;
}
карта с открытым исходным кодом
config.devtool('cheap-source-map');
└── dist
│── app.bundle.js
│── app.bundle.js.map
│── app.css
│── app.css.map
└── index.html
Под исходным файлом будет строка комментариев, доказывающая, что исходная карта включена.
/*# sourceMappingURL=app.css.map*/
Урок 4: оптимизация производительности веб-пакета
В этой главе объясняется
- Отдельный манифест
- Разделение кода
- Разделение пакетов
- Tree Shaking (удалить мертвый код)
- включить gzip
Отдельный манифест
module.exports = (config, resolve) => {
return () => {
config
.optimization
.runtimeChunk({
name: "manifest"
})
}
}
Code Splitting
- Используйте динамический импорт или синтаксис require.ensure, описанный в первом разделе.
- использовать
babel-plugin-import
Плагины вводят некоторые библиотеки компонентов по мере необходимости.
Bundle Splitting
Извлеките общедоступные пакеты вchunk-vendors
Внутри, например, если вы требуете('vue'), webpack упакует vue в chunk-vendors.bundle.js
module.exports = (config, resolve) => {
return () => {
config
.optimization.splitChunks({
chunks: 'async',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 3,
maxInitialRequests: 3,
cacheGroups: {
vendors: {
name: `chunk-vendors`,
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
config.optimization.usedExports(true)
}
}
Tree Shaking
config/optimization.js
config.optimization.usedExports(true);
src/treeShaking.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
Только куб упоминается в main.js
import { cube } from './treeShaking';
console.log(cube(2));
Не использовать встряхивание дерева
{
"./src/treeShaking.js": function(
module,
__webpack_exports__,
__webpack_require__
) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "square", function() {
return square;
});
__webpack_require__.d(__webpack_exports__, "cube", function() {
return cube;
});
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
}
}
Подержанное встряхивание дерева
Здесь экспортируется только функция куба, а квадрат не экспортируется
Конечно, вы можете видеть, что квадратная функция все еще находится в комплекте, но она будет убита во время сжатия, потому что на нее нет ссылки.
{
"./src/treeShaking.js": function(
module,
__webpack_exports__,
__webpack_require__
) {
"use strict";
__webpack_require__.d(__webpack_exports__, "a", function() {
return cube;
});
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
}
}
Встряхивание может быть безопасно выполнено только тогда, когда функция получает ввод и генерирует соответствующий вывод без изменения каких-либо внешних вещей.
Как использовать встряхивание деревьев?
- Убедитесь, что код в формате es6, т.е. экспорт, импорт
- В package.json установите sideEffects
- Убедитесь, что функции встряхивания дерева не имеют побочных эффектов
- Установить пресеты в babelrc [["@babel/preset-env", { "modules": false }]], чтобы запретить преобразование модулей и передать их в webpack для модульной обработки
- В сочетании с uglifyjs-webpack-plugin
по фактуwebpack4
Нам вообще не нужно делать эти операции, т.к.webpack
В продакшене он у нас добавлен по умолчанию, из коробки!
включить gzip
CompressionWebpackPlugin.js
const CompressionWebpackPlugin = require('compression-webpack-plugin');
module.exports = (config, resolve) => {
return () => {
config.plugin('CompressionWebpackPlugin').use(CompressionWebpackPlugin, [
{
algorithm: 'gzip',
test: /\.js(\?.*)?$/i,
threshold: 10240,
minRatio: 0.8
}
]);
};
};
Урок 5: Рукописный загрузчик реализует необязательную цепочку
Содержание этой главы
что такое загрузчик webpack
webpack loader
даwebpack
Промежуточный уровень для обработки различных типов файлов,webpack
По существуnode
модуль, который не может справитьсяjs
кроме файла, тоloader
просто помогитеwebpack
Уровень преобразования выполняется для преобразования всех файлов в строки, вы можете выполнять произвольные операции/модификации строк, а затем возвращаться кwebpack
объект, содержащий эту строку, пустьwebpack
Выполните последующую обработку. если поставитьwebpack
Как мусорный завод, тоloader
Это классификация мусора этой фабрики!
Дополнительное введение цепи
Это не необязательная цепочка в чистом смысле, потому чтоbabel
иts
Он уже поддерживается, и нам не нужно писать полную необязательную цепочку, просто чтобы углубитьloader
понимание,loader
Чем вы можете помочь нам в работе?
用途
Когда мы обращаемся к свойству объекта, нам не нужно беспокоиться о том, что объектundefined
И сообщается об ошибке, в результате чего программа не продолжает выполняться вниз
解释
существует?
Все предыдущие ссылки доступа являются законными и не будут генерировать ошибки
const obj = {
foo: {
bar: {
baz: 2
}
}
}
console.log(obj.foo.bar?.baz) // 2
// 被转成 obj && obj.foo && obj.foo.bar && obj.foo.bar.baz
console.log(obj.foo.err?.baz) // undefined
// 被转成 obj && obj.foo && obj.foo.err && obj.foo.err.baz
загрузчик реализует необязательную цепочку
Загрузчик конфигурации, options-chain-loader
config/OptionsChainLoader.js
module.exports = (config, resolve) => {
const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
const normalRule = baseRule.oneOf('normal');
return () => {
normalRule
.use('options-chain')
.loader(resolve('options-chain-loader'))
}
}
По сути, это обычная замена.loader
Преобразовать весь файл в строку,content
это содержимое всего файла, даcontent
Внесите изменения и верните новый после завершения модификации.content
только что закончил одинloader
конвертировать. Разве это не просто?
Следующая операция означает, что мы сопоставляемobj.foo.bar?.
и превратить его вobj && obj.foo && obj.foo.bar && obj.foo.bar.
options-chain-loader.js
module.exports = function(content) {
return content.replace(new RegExp(/([\$_\w\.]+\?\.)/,'g'),function(res) {
let str = res.replace(/\?\./,'');
let arrs = str.split('.');
let strArr = [];
for(let i = 1; i <= arrs.length; i++) {
strArr.push(arrs.slice(0,i).join('.'));
}
let compile = strArr.join('&&');
const done = compile + '&&' + str + '.'
return done;
});
};
Урок 6: оптимизация компиляции веб-пакета
Содержание этой главы
cache-loader
cache-loader
В основном это кеширование упакованных файлов в каталоге на жестком диске, который обычно существует.node_modules/.cache
вниз, когда ты сноваbuild
Когда файл не изменен, скомпилированный файл будет прочитан из кеша, и будут скомпилированы только измененные файлы, что значительно сократит время компиляции. Особенно когда проект большой.
Сравнение данных до и после использования этого проекта 3342 мс --> 2432 мс Эффект все еще относительно очевиден.
Здесь в babel добавлен только cache-loader, т.к. наши ts/js компилируются babel, и ts-loader кешировать не нужно (мы его тоже не использовали)
config/cacheLoader.js
module.exports = (config, resolve) => {
const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
const babelPath = resolve('babel.js')
const babelConf = require(babelPath);
const version = require(resolve('node_modules/@babel/core/package.json')).version
return () => {
baseRule
.exclude
.add(filepath => {
// 不缓存 node_modules 下的文件
return /node_modules/.test(filepath)
})
.end()
.use('cache-loader')
.loader('cache-loader')
.options({
// 缓存位置
cacheDirectory: resolve('node_modules/.cache/babel')
})
}
}
DllPlugin
DllPlugin должен изолировать долгосрочный неизменный пакет третьей стороны от фактического проекта и упаковать его отдельно.Когда мы собираем, можно импортировать упакованный пакет dll.
Я извлек два пакета vue и react, и скорость увеличилась почти на 200 мс, с 2698 мс до 2377 мс.
пакет DLL
build/dll.js
const path = require("path");
const dllPath = path.join(process.cwd(), 'dll');
const Config = require('webpack-chain');
const config = new Config();
const webpack = require('webpack')
const rimraf = require('rimraf');
const ora = require('ora')
const chalk = require('chalk')
const BundleAnalyzerPlugin = require('../config/BundleAnalyzerPlugin')(config)
BundleAnalyzerPlugin()
config
.entry('dll')
.add('vue')
.add('react')
.end()
.set('mode', "production")
.output
.path(dllPath)
.filename('[name].js')
.library("[name]")
.end()
.plugin('DllPlugin')
.use(webpack.DllPlugin, [{
name: "[name]",
path: path.join(process.cwd(), 'dll', 'manifest.json'),
}])
.end()
rimraf.sync(path.join(process.cwd(), 'dll'))
const spinner = ora('开始构建项目...')
spinner.start()
webpack(config.toConfig(), function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
if (stats.hasErrors()) {
console.log(chalk.red('构建失败\n'))
process.exit(1)
}
console.log(chalk.cyan('build完成\n'))
})
Объединить пакеты dll
const webpack = require('webpack')
module.exports = (config, resolve) => {
return () => {
config.plugin('DllPlugin')
.use(webpack.DllReferencePlugin, [{
context: process.cwd(),
manifest: require(resolve('dll/manifest.json'))
}])
}
}
threadLoader
Эффект теста хуже 😅, чем меньше потоков, тем быстрее скорость компиляции
config/threadLoader.js
module.exports = (config, resolve) => {
const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
return () => {
const useThreads = true;
if (useThreads) {
const threadLoaderConfig = baseRule
.use('thread-loader')
.loader('thread-loader');
threadLoaderConfig.options({ workers: 3 })
}
}
}
Урок 7: Многостраничная конфигурация
Уведомление
-
弃用
npm run build & npm run dev & npm run dll -
改成
box build & box dev & box dll -
link
Ссылка npm глобально связывает команду box
Содержание этой главы
использовать
box build # 不加参数则会编译所有页面,并清空 dist
box dev # 默认编译 index 页面
参数
# index2 是指定编译的页面。不会清空 dist
# report 开启打包分析
box build index2 --report
box dev index2 --report
Превращен в строительные леса
Разделен на три команды для разных операций
- build
- dev
- dll
bin/box.js
#!/usr/bin/env node
const chalk = require('chalk')
const program = require('commander')
const packageConfig = require('../package.json');
const { cleanArgs } = require('../lib')
const path = require('path')
const __name__ = `build,dev,dll`
let boxConf = {}
let lock = false
try {
boxConf = require(path.join(process.cwd(), 'box.config.js'))()
} catch (error) { }
program
.usage('<command> [options]')
.version(packageConfig.version)
.command('build [app-page]')
.description(`构建开发环境`)
.option('-r, --report', '打包分析报告')
.option('-d, --dll', '合并差分包')
.action(async (name, cmd) => {
const options = cleanArgs(cmd)
const args = Object.assign(options, { name }, boxConf)
if (lock) return
lock = true;
if (boxConf.pages) {
Object.keys(boxConf.pages).forEach(page => {
args.name = page;
require('../build/build')(args)
})
} else {
require('../build/build')(args)
}
})
program
.usage('<command> [options]')
.version(packageConfig.version)
.command('dev [app-page]')
.description(`构建生产环境`)
.option('-d, --dll', '合并差分包')
.action(async (name, cmd) => {
const options = cleanArgs(cmd)
const args = Object.assign(options, { name }, boxConf)
if (lock) return
lock = true;
require('../build/dev')(args)
})
program
.usage('<command> [options]')
.version(packageConfig.version)
.command('dll [app-page]')
.description(`编译差分包`)
.action(async (name, cmd) => {
const options = cleanArgs(cmd)
const args = Object.assign(options, { name }, boxConf)
if (lock) return
lock = true;
require('../build/dll')(args)
})
program.parse(process.argv).args && program.parse(process.argv).args[0];
program.commands.forEach(c => c.on('--help', () => console.log()))
if (process.argv[2] && !__name__.includes(process.argv[2])) {
console.log()
console.log(chalk.red(` 没有找到 ${process.argv[2]} 命令`))
console.log()
program.help()
}
if (!process.argv[2]) {
program.help()
}
Многостраничная конфигурация
box.config.js
module.exports = function (config) {
return {
entry: 'src/main.js', // 默认入口
dist: 'dist', // 默认打包目录
publicPath: '/',
port: 8888,
pages: {
index: {
entry: 'src/main.js',
template: 'public/index.html',
filename: 'index.html',
},
index2: {
entry: 'src/main.js',
template: 'public/index2.html',
filename: 'index2.html',
}
},
chainWebpack(config) {
}
}
}
Урок 8: Написание плагина веб-пакета от руки
Если вебпак рассматривать как фабрику мусора, то загрузчик — это классификация мусора, и весь мусор сортируется и передается в вебпак. плагин как бороться с этим мусором.
Плагин webpack очень прост в написании, то есть нужно знать, когда срабатывают те или иные хуки, а потом писать свою логику в хуках и все ок
-
apply
Функция выполняется, когда веб-пакет вызывает плагин, вы можете думать об этом как о точке входа. -
compiler
Выявляет хуки, связанные со всем жизненным циклом веб-пакета. -
Compilation
Предоставляет более детализированные перехватчики событий, связанные с модулями и зависимостями.
Резюме этого раздела
Реализовать CopyPlugin
Сегодня мы напишем плагин для копирования, после завершения сборки веб-пакета скопируем файлы из целевого каталога в другой каталог.
const fs = require('fs-extra')
const globby = require('globby')
class CopyDirWebpackPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
const opt = this.options
compiler.plugin('done', (stats) => {
if (process.env.NODE_ENV === 'production') {
(async ()=>{
const toFilesPath = await globby([`${opt.to}/**`, '!.git/**'])
toFilesPath.forEach(filePath => fs.removeSync(filePath))
const fromFilesPath = await globby([`${opt.from}/**`])
fromFilesPath.forEach(fromPath => {
const cachePath = fromPath
fromPath = fromPath.replace('dist', opt.to)
const dirpaths = fromPath.substring(0, fromPath.lastIndexOf('/'))
fs.mkdirpSync(dirpaths)
fs.copySync(cachePath, fromPath)
})
console.log(` 完成copy ${opt.from} to ${opt.to}`)
})()
}
});
}
}
module.exports = CopyDirWebpackPlugin
использовать
Скопируйте содержимое упакованного каталога dist в каталог dist2.
const CopyPlugin = require('../webapck-plugin-copy');
module.exports = ({ config }) => {
return () => {
config.plugin('copy-dist')
.use(CopyPlugin, [{
from: 'dist',
to: 'dist2'
}])
}
}
Урок 9: Создание ssr
ssr — это рендеринг на стороне сервера.Преимущество выполнения ssr заключается в том, чтобы справиться с недостатками spa, такими как SEO-оптимизация, кеширование на стороне сервера и другие проблемы.
Сегодня я в основном использую ssr реакции, чтобы сделать простой пример, чтобы каждый мог более четко начать работу.
Краткое содержание этой главы
- Создать сборку коробки: ssr
- скомпилировать сср
- скомпилировать синтаксис jsx
- Запись различает сервер/клиент
- рендеринг на стороне сервера
- резюме
Создать сборку коробки: ssr
Старые правила, приходи первымbox build:ssr
команда, чтобы сделать программу исполняемой
воплощать в жизньbox build:ssr
позвонюbuild/ssr
выполнить компиляцию
program
.usage('<command> [options]')
.version(packageConfig.version)
.command('build:ssr [app-page]')
.description(`服务端渲染`)
.action(async (name, cmd) => {
const options = cleanArgs(cmd);
const args = Object.assign(options, { name }, boxConf);
if (lock) return;
lock = true;
require('../build/ssr')(args);
});
скомпилировать сср
Он ничем не отличается от других компиляций, им стоит жить
- цель указана как режим umd
- глобальный объект это
- Запись изменена на ssr.jsx
.libraryTarget('umd')
.globalObject('this')
build/ssr.js
module.exports = function(options) {
const path = require('path');
const Config = require('webpack-chain');
const config = new Config();
const webpack = require('webpack');
const rimraf = require('rimraf');
const ora = require('ora');
const chalk = require('chalk');
const PATHS = {
build: path.join(process.cwd(), 'static'),
ssrDemo: path.join(process.cwd(), 'src', 'ssr.jsx')
};
require('../config/babelLoader')({ config, tsx: true })();
require('../config/HtmlWebpackPlugin')({
config,
options: {
publicPath: '/',
filename: 'client.ssr.html'
}
})();
config
.entry('ssr')
.add(PATHS.ssrDemo)
.end()
.set('mode', 'development') // production
.output.path(PATHS.build)
.filename('[name].js')
.libraryTarget('umd')
.globalObject('this')
.library('[name]')
.end();
rimraf.sync(path.join(process.cwd(), PATHS.build));
const spinner = ora('开始构建项目...');
spinner.start();
webpack(config.toConfig(), function(err, stats) {
spinner.stop();
if (err) throw err;
process.stdout.write(
stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n'
);
if (stats.hasErrors()) {
console.log(chalk.red('构建失败\n'));
process.exit(1);
}
console.log(chalk.cyan('build完成\n'));
});
};
скомпилировать синтаксис jsx
Поскольку мы пишем в React, мы не можем избежать использования синтаксиса jsx, поэтому нам нужно использоватьbabel-loader
используется в@babel/preset-react
npm i @babel/preset-react -D
config/babelLoader.js
if (tsx) {
babelConf.presets.push('@babel/preset-react');
}
Запись различает сервер/клиент
Различие между серверным и клиентским рендерингом
const React = require("react");
const ReactDOM = require("react-dom");
const SSR = <div onClick={() => alert("hello")}>Hello world</div>;
if (typeof document === "undefined") {
console.log('在服务端渲染')
module.exports = SSR;
} else {
console.log('在客户端渲染')
const renderMethod = !module.hot ? ReactDOM.render : ReactDOM.hydrate;
renderMethod(SSR, document.getElementById("app"));
}
рендеринг на стороне сервера
- Используйте упакованную статическую папку в качестве службы
- доступhttp://127.0.0.1:8080, введите страницу, отображаемую сервером
- Выполните ssr.js снова для привязки события
module.exports = function (options) {
const express = require("express");
const { renderToString } = require("react-dom/server");
const chalk = require('chalk')
const SSR = require("../static/ssr");
const port = process.env.PORT || 8080;
server(port);
function server(port) {
const app = express();
app.use(express.static("static"));
app.get("/", (req, res) =>
res.status(200).send(renderMarkup(renderToString(SSR)))
);
const empty = ' '
const common = `App running at:
- Local: http://127.0.0.1:${port}\n`
console.log(chalk.cyan('\n' + empty + common))
app.listen(port, () => process.send && process.send("online"));
}
function renderMarkup(html) {
return `<!DOCTYPE html>
<html>
<head>
<title>Webpack SSR Demo</title>
<meta charset="utf-8" />
</head>
<body>
<div id="app">${html}</div>
<script src="./ssr.js"></script>
</body>
</html>`;
}
}
резюме
Пока ssr закончился.На самом деле все технологии,которые кажутся очень высокими,аккумулируются по крупицам.Пока мы понимаем принцип,тоже можно сделать более качественный фреймворк.
конец
Возможно, это писалось больше двух недель. Я писал понемногу каждый день, и мое самочувствие значительно улучшилось. Если студенты заинтересованы в обучении со мной, они могут прийти и присоединиться ко мне в группе. Я буду организовывать различные группы каждый день по предмету изучения.
Следующими темами, скорее всего, будут:
- Рукописный исходный код vue-next
- ts от входа до оставления
- Узел вход, чтобы плакать
Хаха, просто шучу, вот об этом, есть около одной темы за полда месяца, если у вас хорошая тема, вы можете обсудить это вместе