柚子快報(bào)激活碼778899分享:webpack learn
柚子快報(bào)激活碼778899分享:webpack learn
chunkModules: false
}) + ‘\n\n’
);
if (stats.hasErrors()) {
console.log(chalk.red(‘構(gòu)建失敗\n’));
process.exit(1);
}
console.log(chalk.cyan(‘build完成\n’));
});
構(gòu)建開(kāi)發(fā)環(huá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 參數(shù)
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);
});
});
});
// 監(jiān)聽(tīng)端口
server.listen(port);
new Promise(() => {
compiler.hooks.done.tap(‘dev’, stats => {
const empty = ’ ';
const common = `App running at:
Local: http://127.0.0.1:
p
o
r
t
{port}
port{publicPath}\n`;
console.log(chalk.cyan(‘\n’ + empty + common));
});
});
提取 css
config/css.js
css 提取 loader 配置
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);
};
};
自動(dòng)生成 html
config/HtmlWebpackPlugin.js
const HtmlWebpackPlugin = require(‘html-webpack-plugin’);
module.exports = (config, resolve) => {
return () => {
config.plugin(‘html’).use(HtmlWebpackPlugin, [
{
template: ‘public/index.html’
}
]);
};
};
項(xiàng)目測(cè)試
測(cè)試 html 模板
public/index.html
learn_webpack
測(cè)試 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:基礎(chǔ)配置之loader
本章提要:
配置 babel 使用 babel 配置 ts ts 靜態(tài)類(lèi)型檢查 友好錯(cuò)誤提示插件 配置樣式,style,css、less、sass、postcss 等 postcss 配置 編譯前后 css 對(duì)比 配置 autoprefixer 開(kāi)啟 source map
目錄
增加以下文件
│──── config // 配置目錄
│ │── babelLoader.js // babel-loader 配置
│ │── ForkTsChecker.js // ts 靜態(tài)檢查
│ │── FriendlyErrorsWebpackPlugin.js // 友好錯(cuò)誤提示
│ └── style
│──── src // 開(kāi)發(fā)目錄
│ │── style
│ │ │── app.css
│ │ │── index.less // 測(cè)試 less
│ │ │── index.scss // 測(cè)試 sass
│ │ └── index.postcss // 測(cè)試 postcss
│ └── ts
│ └── index.ts // 測(cè)試 ts
│── babel.js
│── postcss.config.js // postcss 配置
│── tsconfig.json // ts 配置
└──── dist // 打包后的目錄
│── app.bundle.js
│── app.css
└── index.html
配置 babel
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 }));
};
};
使用 babel 配置 ts
這里我們使用?babel?插件?@babel/preset-typescript?將?ts?轉(zhuǎn)成?js,并使用?ForkTsCheckerWebpackPlugin、ForkTsCheckerNotifierWebpackPlugin?插件進(jìn)行錯(cuò)誤提示。
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 靜態(tài)類(lèi)型檢查
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設(shè)為false,可以阻止Webpack的emit以等待類(lèi)型檢查器/linter,并向Webpack的編譯添加錯(cuò)誤。
async: false
}
]);
// 將TypeScript類(lèi)型檢查錯(cuò)誤以彈框提示
// 如果fork-ts-checker-webpack-plugin的async為false時(shí)可以不用
// 否則建議使用,以方便發(fā)現(xiàn)錯(cuò)誤
config.plugin(‘ts-notifier’).use(ForkTsCheckerNotifierWebpackPlugin, [
{
title: ‘TypeScript’,
excludeWarnings: true,
skipSuccessful: true
}
]);
};
};
友好錯(cuò)誤提示插件
config/FriendlyErrorsWebpackPlugin.js
const FriendlyErrorsWebpackPlugin = require(‘friendly-errors-webpack-plugin’);
module.exports = (config, resolve) => {
return () => {
config.plugin(‘error’).use(FriendlyErrorsWebpackPlugin);
};
};
配置樣式,style,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 對(duì)比
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;
}
配置 autoprefixer
自動(dòng)添加 css 前綴
postcss.config.js
module.exports = {
plugins: {
autoprefixer: {
overrideBrowserslist: [
‘> 1%’,
‘last 3 versions’,
‘iOS >= 8’,
‘Android >= 4’,
‘Chrome >= 40’
]
}
}
};
轉(zhuǎn)換前
/* index.css */
.test {
width: 200px;
height: 200px;
color: red;
display: flex;
background-color: orange;
}
轉(zhuǎn)換后
/* 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;
}
開(kāi)啟 source map
config.devtool(‘cheap-source-map’);
└── dist
│── app.bundle.js
│── app.bundle.js.map
│── app.css
│── app.css.map
└── index.html
在源文件下會(huì)有一行注釋?zhuān)C明開(kāi)啟了 sourcemap
/# sourceMappingURL=app.css.map/
課時(shí) 4:webpack性能優(yōu)化
本章講解
分離 Manifest Code Splitting(代碼分割) Bundle Splitting(打包分割) Tree Shaking(刪除死代碼) 開(kāi)啟 gzip
分離 Manifest
module.exports = (config, resolve) => {
return () => {
config
.optimization
.runtimeChunk({
name: “manifest”
})
}
}
Code Splitting
使用動(dòng)態(tài) import 或者 require.ensure 語(yǔ)法,在第一節(jié)已經(jīng)講解 使用?babel-plugin-import?插件按需引入一些組件庫(kù)
Bundle Splitting
將公共的包提取到?chunk-vendors?里面,比如你require(‘vue’),webpack 會(huì)將 vue 打包進(jìn) chunk-vendors.bundle.js
module.exports = (config, resolve) => {
return () => {
config
.optimization.splitChunks({
chunks: ‘a(chǎn)sync’,
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 中只引用了 cube
import { cube } from ‘./treeShaking’;
console.log(cube(2));
未使用 Tree Shaking
{
“./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;
}
}
}
使用了 Tree Shaking
這里只導(dǎo)出了 cube 函數(shù),并沒(méi)有將 square 導(dǎo)出去
當(dāng)然你可以看見(jiàn) square 函數(shù)還是在 bundle 里面,但是在壓縮的時(shí)候就會(huì)被干掉了,因?yàn)樗](méi)有被引用
{
“./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;
}
}
}
只有當(dāng)函數(shù)給定輸入后,產(chǎn)生相應(yīng)的輸出,且不修改任何外部的東西,才可以安全做shaking的操作
如何使用tree-shaking?
確保代碼是es6格式,即 export,import package.json中,設(shè)置 sideEffects 確保 tree-shaking 的函數(shù)沒(méi)有副作用 babelrc中設(shè)置presets [[“@babel/preset-env”, { “modules”: false }]] 禁止轉(zhuǎn)換模塊,交由webpack進(jìn)行模塊化處理 結(jié)合uglifyjs-webpack-plugin
其實(shí)在?webpack4?我們根本不需要做這些操作了,因?yàn)?webpack?在生產(chǎn)環(huán)境已經(jīng)幫我們默認(rèn)添加好了,開(kāi)箱即用!
開(kāi)啟 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
}
]);
};
};
課時(shí) 5:手寫(xiě)loader實(shí)現(xiàn)可選鏈
本章內(nèi)容
什么是 webpack loader 可選鏈介紹 loader 實(shí)現(xiàn)可選鏈
什么是 webpack loader
webpack loader?是?webpack?為了處理各種類(lèi)型文件的一個(gè)中間層,webpack?本質(zhì)上就是一個(gè)?node?模塊,它不能處理?js?以外的文件,那么?loader?就幫助?webpack?做了一層轉(zhuǎn)換,將所有文件都轉(zhuǎn)成字符串,你可以對(duì)字符串進(jìn)行任意操作/修改,然后返回給?webpack?一個(gè)包含這個(gè)字符串的對(duì)象,讓?webpack?進(jìn)行后面的處理。如果把?webpack?當(dāng)成一個(gè)垃圾工廠的話(huà),那么?loader?就是這個(gè)工廠的垃圾分類(lèi)!
可選鏈介紹
這里并不是純粹意義上的可選鏈,因?yàn)?babel?跟?ts?都已經(jīng)支持了,我們也沒(méi)有必要去寫(xiě)一個(gè)完整的可選鏈,只是來(lái)加深一下對(duì)?loader?的理解,?loader?在工作當(dāng)中能幫助我們做什么?
用途?當(dāng)我們?cè)L問(wèn)一個(gè)對(duì)象屬性時(shí)不必?fù)?dān)心這個(gè)對(duì)象是?undefined?而報(bào)錯(cuò),導(dǎo)致程序不能繼續(xù)向下執(zhí)行
解釋?在???之前的所有訪問(wèn)鏈路都是合法的,不會(huì)產(chǎn)生報(bào)錯(cuò)
const obj = {
foo: {
bar: {
baz: 2
}
}
}
console.log(obj.foo.bar?.baz) // 2
// 被轉(zhuǎn)成 obj && obj.foo && obj.foo.bar && obj.foo.bar.baz
console.log(obj.foo.err?.baz) // undefined
// 被轉(zhuǎn)成 obj && obj.foo && obj.foo.err && obj.foo.err.baz
loader 實(shí)現(xiàn)可選鏈
配置loader,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’))
}
}
其實(shí)就是正則替換,loader?將整個(gè)文件全部轉(zhuǎn)換成字符串,content?就是整個(gè)文件的內(nèi)容,對(duì)?content?進(jìn)行修改,修改完成后再返回一個(gè)新的?content?就完成了一個(gè)?loader?轉(zhuǎn)換。是不是很簡(jiǎn)單?
下面的操作意思就是,我們匹配?obj.foo.bar?.?并把它轉(zhuǎn)成?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;
});
};
課時(shí) 6:webpack編譯優(yōu)化
本章內(nèi)容
cache-loader DllPlugin threadLoader
cache-loader
cache-loader?主要是將打包好的文件緩存在硬盤(pán)的一個(gè)目錄里,一般存在?node_modules/.cache?下,當(dāng)你再次?build?的時(shí)候如果此文件沒(méi)有修改就會(huì)從緩存中讀取已經(jīng)編譯過(guò)的文件,只有有改動(dòng)的才會(huì)被編譯,這樣就大大降低了編譯的時(shí)間。尤其是項(xiàng)目越大時(shí)越明顯。
此項(xiàng)目使用前后數(shù)據(jù)對(duì)比 3342ms --> 2432ms 效果還是比較明顯
這里只對(duì) babel 加入了 cache-loader,因?yàn)槲覀兊?ts/js 都是由 babel 進(jìn)行編譯的,不需要對(duì) ts-loader 緩存(我們也沒(méi)有用到)
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 是將第三方長(zhǎng)期不變的包與實(shí)際項(xiàng)目隔離開(kāi)來(lái)并分別打包,當(dāng)我們 build 時(shí)再將已經(jīng)打包好的 dll 包引進(jìn)來(lái)就 ok 了
我提取了兩個(gè)包 vue、react,速度差不多提升了 200ms,從 2698ms 到 2377ms
打包 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(‘開(kāi)始構(gòu)建項(xiàng)目…’)
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(‘構(gòu)建失敗\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
測(cè)試效果變差了 ???,線(xiàn)程數(shù)越小編譯速度越快
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 })
}
}
}
課時(shí) 7:多頁(yè)面配置
注意
棄用?npm run build & npm run dev & npm run dll 改成?box build & box dev & box dll link?npm link 將 box 命令鏈接到全局
本章內(nèi)容
使用 改造為腳手架 多頁(yè)面配置
使用
box build # 不加參數(shù)則會(huì)編譯所有頁(yè)面,并清空 dist
box dev # 默認(rèn)編譯 index 頁(yè)面
參數(shù)
index2 是指定編譯的頁(yè)面。不會(huì)清空 dist
report 開(kāi)啟打包分析
box build index2 --report
box dev index2 --report
改造為腳手架
分成三個(gè)命令,進(jìn)行不同操作
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(‘ [options]’)
.version(packageConfig.version)
.command(‘build [app-page]’)
.description(構(gòu)建開(kāi)發(fā)環(huán)境)
.option(‘-r, --report’, ‘打包分析報(bào)告’)
.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(‘ [options]’)
.version(packageConfig.version)
.command(‘dev [app-page]’)
.description(構(gòu)建生產(chǎn)環(huán)境)
.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(‘ [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( 沒(méi)有找到 ${process.argv[2]} 命令))
console.log()
program.help()
}
if (!process.argv[2]) {
program.help()
}
多頁(yè)面配置
box.config.js
module.exports = function (config) {
return {
entry: ‘src/main.js’, // 默認(rèn)入口
dist: ‘dist’, // 默認(rèn)打包目錄
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) {
}
}
}
課時(shí) 8:手寫(xiě)一個(gè)webpack插件
如果把 webpack 當(dāng)成一個(gè)垃圾工廠,loader 就是垃圾分類(lèi),將所有垃圾整理好交給 webpack。plugin 就是如何去處理這些垃圾。
webpack 插件寫(xiě)起來(lái)很簡(jiǎn)單,就是你要知道各種各樣的鉤子在什么時(shí)候觸發(fā),然后你的邏輯寫(xiě)在鉤子里面就ok了
apply?函數(shù)是 webpack 在調(diào)用 plugin 的時(shí)候執(zhí)行的,你可以認(rèn)為它是入口 compiler?暴露了和 webpack 整個(gè)生命周期相關(guān)的鉤子 Compilation?暴露了與模塊和依賴(lài)有關(guān)的粒度更小的事件鉤子
本節(jié)概要
實(shí)現(xiàn)一個(gè) CopyPlugin 使用
實(shí)現(xiàn)一個(gè) CopyPlugin
我們今天寫(xiě)一個(gè) copy 的插件,在webpack構(gòu)建完成之后,將目標(biāo)目錄下的文件 copy 到另一個(gè)目錄下
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
使用
將打包出來(lái)的 dist 目錄下的內(nèi)容 copy 到 dist2 目錄下
const CopyPlugin = require(‘…/webapck-plugin-copy’);
module.exports = ({ config }) => {
return () => {
config.plugin(‘copy-dist’)
.use(CopyPlugin, [{
from: ‘dist’,
to: ‘dist2’
}])
}
}
課時(shí) 9:構(gòu)建 ssr
ssr 就是服務(wù)端渲染,做 ssr 的好處就是為了處理 spa 的不足,比如 seo 優(yōu)化,服務(wù)端緩存等問(wèn)題。
今天主要用 react 的 ssr 來(lái)做一個(gè)簡(jiǎn)單的實(shí)例,讓大家更清晰的入門(mén)
本章概要
創(chuàng)建 box build:ssr 編譯 ssr 編譯 jsx 語(yǔ)法 入口區(qū)分服務(wù)端/客戶(hù)端 服務(wù)端渲染 小結(jié)
創(chuàng)建 box build:ssr
老規(guī)矩,先來(lái)一個(gè)?box build:ssr?命令讓程序可以執(zhí)行
執(zhí)行?box build:ssr?會(huì)調(diào)用?build/ssr?執(zhí)行編譯
program
.usage(‘ [options]’)
.version(packageConfig.version)
.command(‘build:ssr [app-page]’)
.description(服務(wù)端渲染)
.action(async (name, cmd) => {
const options = cleanArgs(cmd);
const args = Object.assign(options, { name }, boxConf);
if (lock) return;
lock = true;
require(‘…/build/ssr’)(args);
});
編譯 ssr
與其他的編譯沒(méi)有什么區(qū)別,值得住的是
target 指定為 umd 模式 globalObject 為 this 入口改為 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(‘開(kāi)始構(gòu)建項(xiàng)目…’);
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(‘構(gòu)建失敗\n’));
process.exit(1);
}
console.log(chalk.cyan(‘build完成\n’));
});
};
編譯 jsx 語(yǔ)法
因?yàn)槲覀兪怯?react 寫(xiě)的,避免不了會(huì)用到 jsx 語(yǔ)法,所以我們需要在?babel-loader?中使用?@babel/preset-react
npm i @babel/preset-react -D
config/babelLoader.js
if (tsx) {
babelConf.presets.push(‘@babel/preset-react’);
}
入口區(qū)分服務(wù)端/客戶(hù)端
區(qū)分服務(wù)端跟客戶(hù)端分別渲染
const React = require(“react”);
const ReactDOM = require(“react-dom”);
const SSR =
if (typeof document === “undefined”) {
console.log(‘在服務(wù)端渲染’)
module.exports = SSR;
} else {
console.log(‘在客戶(hù)端渲染’)
const renderMethod = !module.hot ? ReactDOM.render : ReactDOM.hydrate;
renderMethod(SSR, document.getElementById(“app”));
} 自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過(guò),也去過(guò)華為、OPPO等大廠,18年進(jìn)入阿里一直到現(xiàn)在。
深知大多數(shù)前端工程師,想要提升技能,往往是自己摸索成長(zhǎng)或者是報(bào)班學(xué)習(xí),但對(duì)于培訓(xùn)機(jī)構(gòu)動(dòng)則幾千的學(xué)費(fèi),著實(shí)壓力不小。自己不成體系的自學(xué)效果低效又漫長(zhǎng),而且極易碰到天花板技術(shù)停滯不前!
因此收集整理了一份《2024年Web前端開(kāi)發(fā)全套學(xué)習(xí)資料》,初衷也很簡(jiǎn)單,就是希望能夠幫助到想自學(xué)提升又不知道該從何學(xué)起的朋友,同時(shí)減輕大家的負(fù)擔(dān)。
既有適合小白學(xué)習(xí)的零基礎(chǔ)資料,也有適合3年以上經(jīng)驗(yàn)的小伙伴深入學(xué)習(xí)提升的進(jìn)階課程,基本涵蓋了95%以上前端開(kāi)發(fā)知識(shí)點(diǎn),真正體系化!
由于文件比較大,這里只是將部分目錄截圖出來(lái),每個(gè)節(jié)點(diǎn)里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實(shí)戰(zhàn)項(xiàng)目、講解視頻,并且會(huì)持續(xù)更新!
如果你覺(jué)得這些內(nèi)容對(duì)你有幫助,可以?huà)叽a獲取?。。▊渥ⅲ呵岸耍?/p>
最后
今天的文章可謂是積蓄了我這幾年來(lái)的應(yīng)聘和面試經(jīng)歷總結(jié)出來(lái)的經(jīng)驗(yàn),干貨滿(mǎn)滿(mǎn)呀!如果你能夠一直堅(jiān)持看到這兒,那么首先我還是十分佩服你的毅力的。不過(guò)光是看完而不去付出行動(dòng),或者直接進(jìn)入你的收藏夾里吃灰,那么我寫(xiě)這篇文章就沒(méi)多大意義了。所以看完之后,還是多多行動(dòng)起來(lái)吧!
可以非常負(fù)責(zé)地說(shuō),如果你能夠堅(jiān)持把我上面列舉的內(nèi)容都一個(gè)不拉地看完并且全部消化為自己的知識(shí)的話(huà),那么你就至少已經(jīng)達(dá)到了中級(jí)開(kāi)發(fā)工程師以上的水平,進(jìn)入大廠技術(shù)這塊是基本沒(méi)有什么問(wèn)題的了。
');
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(‘開(kāi)始構(gòu)建項(xiàng)目…’);
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(‘構(gòu)建失敗\n’));
process.exit(1);
}
console.log(chalk.cyan(‘build完成\n’));
});
};
編譯 jsx 語(yǔ)法
因?yàn)槲覀兪怯?react 寫(xiě)的,避免不了會(huì)用到 jsx 語(yǔ)法,所以我們需要在?babel-loader?中使用?@babel/preset-react
npm i @babel/preset-react -D
config/babelLoader.js
if (tsx) {
babelConf.presets.push(‘@babel/preset-react’);
}
入口區(qū)分服務(wù)端/客戶(hù)端
區(qū)分服務(wù)端跟客戶(hù)端分別渲染
const React = require(“react”);
const ReactDOM = require(“react-dom”);
const SSR =
if (typeof document === “undefined”) {
console.log(‘在服務(wù)端渲染’)
module.exports = SSR;
} else {
console.log(‘在客戶(hù)端渲染’)
const renderMethod = !module.hot ? ReactDOM.render : ReactDOM.hydrate;
renderMethod(SSR, document.getElementById(“app”));
} 自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過(guò),也去過(guò)華為、OPPO等大廠,18年進(jìn)入阿里一直到現(xiàn)在。
深知大多數(shù)前端工程師,想要提升技能,往往是自己摸索成長(zhǎng)或者是報(bào)班學(xué)習(xí),但對(duì)于培訓(xùn)機(jī)構(gòu)動(dòng)則幾千的學(xué)費(fèi),著實(shí)壓力不小。自己不成體系的自學(xué)效果低效又漫長(zhǎng),而且極易碰到天花板技術(shù)停滯不前!
因此收集整理了一份《2024年Web前端開(kāi)發(fā)全套學(xué)習(xí)資料》,初衷也很簡(jiǎn)單,就是希望能夠幫助到想自學(xué)提升又不知道該從何學(xué)起的朋友,同時(shí)減輕大家的負(fù)擔(dān)。
[外鏈圖片轉(zhuǎn)存中…(img-Qvv7B7zA-1712234940229)]
[外鏈圖片轉(zhuǎn)存中…(img-ms78VScl-1712234940230)]
既有適合小白學(xué)習(xí)的零基礎(chǔ)資料,也有適合3年以上經(jīng)驗(yàn)的小伙伴深入學(xué)習(xí)提升的進(jìn)階課程,基本涵蓋了95%以上前端開(kāi)發(fā)知識(shí)點(diǎn),真正體系化!
[外鏈圖片轉(zhuǎn)存中…(img-sqGZb0zj-1712234940230)]
由于文件比較大,這里只是將部分目錄截圖出來(lái),每個(gè)節(jié)點(diǎn)里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實(shí)戰(zhàn)項(xiàng)目、講解視頻,并且會(huì)持續(xù)更新!
如果你覺(jué)得這些內(nèi)容對(duì)你有幫助,可以?huà)叽a獲?。。。▊渥ⅲ呵岸耍?/p>
最后
今天的文章可謂是積蓄了我這幾年來(lái)的應(yīng)聘和面試經(jīng)歷總結(jié)出來(lái)的經(jīng)驗(yàn),干貨滿(mǎn)滿(mǎn)呀!如果你能夠一直堅(jiān)持看到這兒,那么首先我還是十分佩服你的毅力的。不過(guò)光是看完而不去付出行動(dòng),或者直接進(jìn)入你的收藏夾里吃灰,那么我寫(xiě)這篇文章就沒(méi)多大意義了。所以看完之后,還是多多行動(dòng)起來(lái)吧!
可以非常負(fù)責(zé)地說(shuō),如果你能夠堅(jiān)持把我上面列舉的內(nèi)容都一個(gè)不拉地看完并且全部消化為自己的知識(shí)的話(huà),那么你就至少已經(jīng)達(dá)到了中級(jí)開(kāi)發(fā)工程師以上的水平,進(jìn)入大廠技術(shù)這塊是基本沒(méi)有什么問(wèn)題的了。
資料領(lǐng)取方式:戳這里前往免費(fèi)領(lǐng)取
柚子快報(bào)激活碼778899分享:webpack learn
好文鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。