webpack深入浅出实战系列(1)

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: 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);

};

};

自动生成 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

learn_webpack
测试 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:基础配置之loader


本章提要:

  • 配置 babel

  • 使用 babel 配置 ts

  • ts 静态类型检查

  • 友好错误提示插件

  • 配置样式,style,css、less、sass、postcss 等

  • postcss 配置

  • 编译前后 css 对比

  • 配置 autoprefixer

  • 开启 source map

目录

增加以下文件

│──── 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

配置 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 转成 js,并使用 ForkTsCheckerWebpackPluginForkTsCheckerNotifierWebpackPlugin 插件进行错误提示。

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);

};

};

配置样式,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 对比

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

自动添加 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;

}

开启 source map

config.devtool(‘cheap-source-map’);

└── dist

│── app.bundle.js

│── app.bundle.js.map

│── app.css

│── app.css.map

└── index.html

在源文件下会有一行注释,证明开启了 sourcemap

/# sourceMappingURL=app.css.map/


课时 4:webpack性能优化


本章讲解

  1. 分离 Manifest

  2. Code Splitting(代码分割)

  3. Bundle Splitting(打包分割)

  4. Tree Shaking(删除死代码)

  5. 开启 gzip

分离 Manifest

module.exports = (config, resolve) => {

return () => {

config

.optimization

.runtimeChunk({

name: “manifest”

})

}

}

Code Splitting

  1. 使用动态 import 或者 require.ensure 语法,在第一节已经讲解

  2. 使用 babel-plugin-import 插件按需引入一些组件库

Bundle Splitting

将公共的包提取到 chunk-vendors 里面,比如你require(‘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 中只引用了 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

这里只导出了 cube 函数,并没有将 square 导出去

当然你可以看见 square 函数还是在 bundle 里面,但是在压缩的时候就会被干掉了,因为它并没有被引用

{

“./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;

}

}

}

只有当函数给定输入后,产生相应的输出,且不修改任何外部的东西,才可以安全做shaking的操作

如何使用tree-shaking?

  1. 确保代码是es6格式,即 export,import

  2. package.json中,设置 sideEffects

  3. 确保 tree-shaking 的函数没有副作用

  4. babelrc中设置presets [[“@babel/preset-env”, { “modules”: false }]] 禁止转换模块,交由webpack进行模块化处理

  5. 结合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:手写loader实现可选链


本章内容

  1. 什么是 webpack loader

  2. 可选链介绍

  3. loader 实现可选链

什么是 webpack loader

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

loader 实现可选链

配置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’))

}

}

其实就是正则替换,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:webpack编译优化


本章内容

  1. cache-loader

  2. DllPlugin

  3. threadLoader

cache-loader

cache-loader 主要是将打包好的文件缓存在硬盘的一个目录里,一般存在 node_modules/.cache 下,当你再次 build 的时候如果此文件没有修改就会从缓存中读取已经编译过的文件,只有有改动的才会被编译,这样就大大降低了编译的时间。尤其是项目越大时越明显。

此项目使用前后数据对比 3342ms --> 2432ms 效果还是比较明显

这里只对 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 是将第三方长期不变的包与实际项目隔离开来并分别打包,当我们 build 时再将已经打包好的 dll 包引进来就 ok 了

我提取了两个包 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(‘开始构建项目…’)

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 link 将 box 命令链接到全局

本章内容

  1. 使用

  2. 改造为脚手架

  3. 多页面配置

使用

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(‘ [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(‘ [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(‘ [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插件


如果把 webpack 当成一个垃圾工厂,loader 就是垃圾分类,将所有垃圾整理好交给 webpack。plugin 就是如何去处理这些垃圾。

webpack 插件写起来很简单,就是你要知道各种各样的钩子在什么时候触发,然后你的逻辑写在钩子里面就ok了

  • apply 函数是 webpack 在调用 plugin 的时候执行的,你可以认为它是入口

  • compiler 暴露了和 webpack 整个生命周期相关的钩子

  • Compilation 暴露了与模块和依赖有关的粒度更小的事件钩子

本节概要

  • 实现一个 CopyPlugin

  • 使用

实现一个 CopyPlugin

我们今天写一个 copy 的插件,在webpack构建完成之后,将目标目录下的文件 copy 到另一个目录下

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 目录下的内容 copy 到 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 优化,服务端缓存等问题。

今天主要用 react 的 ssr 来做一个简单的实例,让大家更清晰的入门

本章概要

  • 创建 box build:ssr

  • 编译 ssr

  • 编译 jsx 语法

  • 入口区分服务端/客户端

  • 服务端渲染

  • 小结

创建 box build:ssr

老规矩,先来一个 box build:ssr 命令让程序可以执行

执行 box build:ssr 会调用 build/ssr 执行编译

program

.usage(‘ [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);

});

编译 ssr

与其他的编译没有什么区别,值得住的是

  • 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(‘开始构建项目…’);

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;

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”));

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

后记


总结一下这三次面试下来我的经验是:

  1. 一定不要死记硬背,要理解原理,否则面试官一深入就会露馅!

  2. 代码能力一定要注重,尤其是很多原理性的代码(之前两次让我写过Node中间件,Promise.all,双向绑定原理,被虐的怀疑人生)!

  3. 尽量从面试官的问题中表现自己知识的深度与广度,让面试官发现你的闪光点!

  4. 多刷面经!

我把所有遇到的面试题都做了一个整理,并且阅读了很多大牛的博客之后写了解析,免费分享给大家,算是一个感恩回馈吧,有需要的朋友【点击我】获取。祝大家早日拿到自己心怡的工作!

篇幅有限,仅展示部分内容



.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;

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”));

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-oXNz3gRZ-1712545781968)]

[外链图片转存中…(img-ehZTlC0N-1712545781969)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-7QpJeavZ-1712545781969)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

后记


总结一下这三次面试下来我的经验是:

  1. 一定不要死记硬背,要理解原理,否则面试官一深入就会露馅!

  2. 代码能力一定要注重,尤其是很多原理性的代码(之前两次让我写过Node中间件,Promise.all,双向绑定原理,被虐的怀疑人生)!

  3. 尽量从面试官的问题中表现自己知识的深度与广度,让面试官发现你的闪光点!

  4. 多刷面经!

我把所有遇到的面试题都做了一个整理,并且阅读了很多大牛的博客之后写了解析,免费分享给大家,算是一个感恩回馈吧,有需要的朋友【点击我】获取。祝大家早日拿到自己心怡的工作!

篇幅有限,仅展示部分内容



  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值