关于javascript:POI-源码阅读

1次阅读

共计 10323 个字符,预计需要花费 26 分钟才能阅读完成。

更好的观看体验移步飞书

https://bytedance.feishu.cn/docs/doccnOadukROqZqGS9uOj6oGVzb

工夫破费

工夫一天两个小时左右,读文档用了两个多小时,一共破费五天 10 个小时的工夫,根本把大略的逻辑(主线)了解分明了。

写文档两个小时。

读 poi 整体感触比 sao 稍微的吃力一些,须要相熟 webpack 的操作配置。

POI 是什么?

就是把 webpack 封装了一层的工具,能够本地启服务和打包。

省去本人的一大堆配置。

这里我要吐槽一下,如果我用 react 和 vue 的官网的那个脚手架,不也很好?还自带 router

如果我真的想改什么不也得看 poi 的文档,不也很麻烦吗?

从整个大方向上,用的用户不是很多。不过好在能够给本人造就教训和晋升技术,从这个意义上说还是有价值的。

import styles from './style.module.css'

const el = document.createElement('div')el.className = styles.titleel.textContent = 'Hello Poi!'

document.body.appendChild(el)

这种写法例子,也不感觉很时尚。

POI 有什么性能?

https://poi.js.org/guide/ 从文档登程,读文档就读了好几个小时

题外话:这个页面的那个淡淡的蓝色,不如深色更好看,因为自身就是浅色模式,这个淡淡的色,很不容易吸引眼球。

最根底的性能有

一个本地开发,一个打包

// 打包,你为啥不写 --build 呢???

poi --prod

// 本地 port 开发模式

poi --dev

create-poi-app 实现了模板搬运,初始化我的项目

除此之外还有一个模板搬运的过程

yarn global add create-poi-app

create-poi-app my-app

细节性能

–inspect-webpack   以默认编辑器关上 webpack 配置

正如文档所说,外面做了各种文件的转译,应用 babel,其实这都是 webpack 干的事啦,只有初始化的时候抉择相应的配置就能够。

不必本人配置啦。

|

除此之外还领有一些代理性能就是 webpack 提供的啦。

你能够把本人想写的配置写到 poi.config.js 这样合并到默认的 webpack.config.js 造成新的配置

POI 是怎么实现的?

地址 https://github.com/egoist/poi

外面应用了很常见的 lerna 实现多包治理,不过最次要的也就两个包啦

|

一个是 create-poi-app,一个是 core/poi

create-poi-app

这个外面比较简单,依据之前读过 sao 的教训,15 分钟就读明确了,不多唠叨。不过也让我晓得了 sao 能够更灵便的使用,sao 的实现的性能就是模板搬运。

const app = sao({

generator: path.join(__dirname, '../generator'),

outDir: targetFolder,

npmClient

})

把模板传入,把输入目录即 outDir 传入,依据配置文件,问你一大堆问题,拿到问题后果搬用。

能够举个简略的例子

拿到你的答复是 ts 还是 js 而后去增加相应的文件,或是增加一些插件和配置

{

type: 'add',

templateDir: `templates/${typeChecker === 'ts' ? 'ts' : 'js'}`,

files: '**',

filters: {

'**/*.test.{js,ts}': Boolean(unit)

}

}

core/poi

上面的稍微的有点难度,不过通过我屡次翻看,终于明确了外围逻辑。

肯定要目不转睛的看这里,这里写的才是整篇文章最重要的。

进入

const poi = new Poi()

_await_ poi.run()

从 bin/cli 下开始进入

这里引入了 require(‘v8-compile-cache’),只是为了更快的速度。

咱们走进 lib/index.js  最简单的就是页面了,讲清楚这个,根本整个我的项目都讲通了。

先理分明几个变量

this.args = parseArgs(rawArgs)

this.args 就是 –serve –prod  –debug –test 之类的货色

this.hooks = new Hooks()

this.hooks 就是一个公布订阅模式,名字和 webpack 的 hook 治理有点像

module.exports = _class_ Hooks {

constructor() {

this.hooks = new Map()

}

add(name, fn) {

const hooks = this.get(name)

hooks.add(fn)

this.hooks.set(name, hooks)

}

get(name) {

_return_ this.hooks.get(name) || new Set()

}

invoke(name, ...args) {

_for_ (const hook of this.get(name)) {

hook(...args)

}

}

async invokePromise(name, ...args) {

_for_ (const hook of this.get(name)) {

_await_ hook(...args)

}

}

}

add 是增加函数,invoke 是执行相应的函数,还增加一个异步执行,这里代码能够好好学习下,比方他应用了 set 和 map 很有意思。

this.cwd = this.args.get('cwd')

cwd 就是你的我的项目门路,是你本人的我的项目门路

this.configLoader = createConfigLoader(this.cwd)

createConfigLoader 这里还是应用  joycon 读取配置

传入你要读取的配置文件

比方

defaultConfigFiles = [

'poi.config.js',

'poi.config.ts',

'package.json',

'.poirc',

'.poirc.json',

'.poirc.js'

]

joycon 会把 path 和配置 data 给读取到

const {path: configPath, data: configFn} = this.configLoader.load({

files: configFiles,

packageKey: 'poi'

})

this.config =

typeof configFn === 'function' ? configFn(this.args.options) : configFn

此时咱们拿到配置文件数据

this.pkg = this.configLoader.load({

files: ['package.json']

})

this.pkg.data = this.pkg.data || {}

拿到你的 package.json 数据

initPlugins

this.plugins = [

{resolve: require.resolve('./plugins/command-options') },

{resolve: require.resolve('./plugins/config-babel') },

{resolve: require.resolve('./plugins/config-vue') },

{resolve: require.resolve('./plugins/config-css') },

{resolve: require.resolve('./plugins/config-font') },

{resolve: require.resolve('./plugins/config-image') },

{resolve: require.resolve('./plugins/config-eval') },

{resolve: require.resolve('./plugins/config-html') },

{resolve: require.resolve('./plugins/config-electron') },

{resolve: require.resolve('./plugins/config-misc-loaders') },

{resolve: require.resolve('./plugins/config-reason') },

{resolve: require.resolve('./plugins/config-yarn-pnp') },

{resolve: require.resolve('./plugins/config-jsx-import') },

{resolve: require.resolve('./plugins/config-react-refresh') },

{resolve: require.resolve('./plugins/watch') },

{resolve: require.resolve('./plugins/serve') },

{resolve: require.resolve('./plugins/eject-html') },

{resolve: require.resolve('@poi/plugin-html-entry') }

]

.concat(mergePlugins(configPlugins, cliPlugins))

.map(plugin => {

_if_ (typeof plugin.resolve === 'string') {

plugin._resolve = plugin.resolve

plugin.resolve = require(plugin.resolve)

}

_return_ plugin

})

给 plugins 加点货色,很重要的货色。合并了 cli 的 plugin 和配置里的 plugin

咱们点进 plugin 看一看

有 exports.cli exports.when  exports.apply 他们别离在不同机会去执行,

api.hook('createWebpackChain', config => {

config.module

.rule('font')

.test(/.(eot|otf|ttf|woff|woff2)(?.*)?_$_/)

.use('file-loader')

.loader(require.resolve('file-loader'))

.options({

name: api.config.output.fileNames.font

})

})

在  apply 外面全是 api.hook createWebpackChain,这样写,只有当我触发 invoke createWebpackChain 的时候,这些函数将会被同时执行。

serve

咱们看最最最重要的 serve,看明确它也就理清外围了

// 拿到默认 webpackConfig 配置,怎么拿到的,上面说

const webpackConfig = api.createWebpackChain().toConfig()

// api 就是 poi 实例,const compiler = require('webpack')(config) 把配置文件传入生成编译后的文件

const compiler = api.createWebpackCompiler(webpackConfig)

// 启动服务的配置,下面的配置是编译 babel 的配置

const devServerConfig = Object.assign(

{

noInfo: true,

historyApiFallback: true,

overlay: false,

disableHostCheck: true,

compress: true,

// _Silence WebpackDevServer's own logs since they're generally not useful._

// _It will still show compile warnings and errors with this setting._

clientLogLevel: 'none',

// _Prevent a WS client from getting injected as we're already including_

// _`webpackHotDevClient`._

injectClient: false,

publicPath: webpackConfig.output.publicPath,

contentBase:

api.config.publicFolder && api.resolveCwd(api.config.publicFolder),

watchContentBase: true,

stats: 'none'

},

devServer,

{

proxy:

typeof devServer.proxy === 'string'

? require('@poi/dev-utils/prepareProxy')(

devServer.proxy,

api.resolveCwd(api.config.publicFolder),

api.cli.options.debug

)

: devServer.proxy

}

)

// 启动服务,监听端口

const WebpackDevServer = require('webpack-dev-server')

const server = new WebpackDevServer(compiler, devServerConfig)

api.hooks.invoke('createServer', { server, port, host})

server.listen(port, host)

这里有点不了解点中央

api.hooks.invoke('beforeDevMiddlewares', server)

api.hooks.invoke('onCreateServer', server) // _TODO:_ _remove this in the future_

api.hooks.invoke('afterDevMiddlewares', server)

api.hooks.invoke('createServer', { server, port, host})

api.hooks.invoke('createDevServerConfig', devServerConfig)

在整套代码里我没有找到任何增加 hook 操作,这些也不是 webpack 的生命周期,我狐疑只是增加钩子给其余的引入里用的

exports.apply = api => {

// 这里 config 拿到的是 webpack 的 config

api.hook('createWebpackChain', config => {

_if_ (!api.cli.options.serve) _return_

// 如果有 hot,给 config 增加 hot 的配置

_if_ (api.config.devServer.hot) {

const hotEntries =

api.config.devServer.hotEntries.length > 0

? api.config.devServer.hotEntries

: config.entryPoints.store.keys()

_for_ (const entry of hotEntries) {

_if_ (config.entryPoints.has(entry)) {

config.entry(entry).prepend('#webpack-hot-client')

}

}

const {HotModuleReplacementPlugin} = require('webpack')

HotModuleReplacementPlugin.__expression = `require('webpack').HotModuleReplacementPlugin`

config.plugin('hot').use(HotModuleReplacementPlugin)

}

})

}

Plugin apply 办法

包含任何其余 plugin  apply 办法里,写的都是通用的,如果有 vue,增加 vue 的 loader

exports.apply = api => {

api.hook('createWebpackChain', config => {

const rule = config.module.rule('vue').test(/.vue_$_/)

...

rule

.use('vue-loader')

.loader(require.resolve(vueLoaderPath))

.options(

Object.assign(

{

// _TODO:_ _error with thread-loader_

compiler: isVue3

? undefined

: api.localRequire('vue-template-compiler')

},

// _For Vue templates_

api.config.cache && getCacheOptions()

)

)

config.plugin('vue').use(require(vueLoaderPath).VueLoaderPlugin)

})

}

其余 css, html, image, babel 都差不多,这些过程很是繁琐,须要相熟 webpack 的配置

总结一下 plugin

在 cli 执行的 args 的命令,在 apply 的时候更改了 webpack 的配置,when 是管制什么时候退出 apply

执行 plugin cli

this.extendCLI()

// 这里执行了 plugin 的 cli,传入了 this

extendCLI() {

_for_ (const plugin of this.plugins) {

_if_ (plugin.resolve.cli) {

plugin.resolve.cli(this, plugin.options)

}

}

}

其实管制执行的是这句话

_await_ this.cli.runMatchedCommand()

找了半天这个办法,原来是 cac 外面的办法,之前配置了 一个 false 的参数就不会被立刻执行

为什么不立刻执行,为了退出几个钩子

_await_ this.hooks.invokePromise('beforeRun')

_await_ this.cli.runMatchedCommand()

_await_ this.hooks.invokePromise('afterRun')

执行 plugin apply

this.mergeConfig()

// _Call plugin.apply_

this.applyPlugins()

applyPlugins() {

let plugins = this.plugins.filter(plugin => {

_return_ !plugin.resolve.when || plugin.resolve.when(this)

})

// _Run plugin's `filterPlugins` method_

_for_ (const plugin of plugins) {

_if_ (plugin.resolve.filterPlugins) {

plugins = plugin.resolve.filterPlugins(this.plugins, plugin.options)

}

}

// _Run plugin's `apply` method_

_for_ (const plugin of plugins) {

_if_ (plugin.resolve.apply) {

logger.debug(`Apply plugin: `${chalk.bold(plugin.resolve.name)}``)

_if_ (plugin._resolve) {

logger.debug(`location: ${plugin._resolve}`)

}

plugin.resolve.apply(this, plugin.options)

}

}

}

先 merge config,而后执行 apply 办法,apply 办法执行,只是退出了函数 hook,真正的执行是这句

this.hooks.invoke('createWebpackChain', config, opts)

咱们回到 initCLI

this.command = cli

.command('[...entries]', 'Entry files to start bundling', {

ignoreOptionDefaultValue: true

})

.usage('[...entries] [options]')

.action(async () => {

logger.debug(`Using default handler`)

const chain = this.createWebpackChain()

const compiler = this.createWebpackCompiler(chain.toConfig())

_await_ this.runCompiler(compiler)

})

进入  createWebpackChain, 进入 utils/webpackChain, 应用 webpack-chain 创立了起初的 webpack 配置

createWebpackChain(opts) {

const WebpackChain = require('./utils/WebpackChain')

opts = Object.assign({type: 'client', mode: this.mode}, opts)

// 退出 poi 的配置,configureWebpack 有趣味能够本人去追踪下

const config = new WebpackChain({

configureWebpack: this.config.configureWebpack,

opts

})

// 退出本地配置

require('./webpack/webpack.config')(config, this)

// 配置好 config,却依据 config,增加 webpack 相应的规定

this.hooks.invoke('createWebpackChain', config, opts)

_if_ (this.config.chainWebpack) {

this.config.chainWebpack(config, opts)

}

// 如果有 --inspect-webpack,应用 open 关上配置,应用的默认 editor

_if_ (this.cli.options.inspectWebpack) {

const inspect = () => {

const id = Math.random()

.toString(36)

.substring(7)

const outFile = path.join(

os.tmpdir(),

`poi-inspect-webpack-config-${id}.js`

)

const configString = `// ${JSON.stringify(

opts

)}nvar config = ${config.toString()}nn`

fs.writeFileSync(outFile, configString, 'utf8')

require('@poi/dev-utils/open')(outFile, {

wait: false

})

}

config.plugin('inspect-webpack').use(

_class_ InspectWebpack {

apply(compiler) {

compiler.hooks.afterEnvironment.tap('inspect-webpack', inspect)

}

}

)

}

// 返回残缺的 webpack 的 config,下面所的一切都是为了配置 webpack 的 config

_return_ config

}

const chain = this.createWebpackChain()

// 依据 config 去编译,生成编译后的文件

const compiler = this.createWebpackCompiler(chain.toConfig())

// 打包编译后果

_await_ this.runCompiler(compiler)

以上最根本的服务和编译打包跑通了

只管在文档里对于 cli 的操作很少,然而实现的却有很多

createConfigFromCLIOptions() {

const {

minimize,

sourceMap,

format,

moduleName,

outDir,

publicUrl,

target,

clean,

parallel,

cache,

jsx,

extractCss,

hot,

host,

port,

open,

proxy,

fileNames,

html,

publicFolder,

babelrc,

babelConfigFile,

reactRefresh

} = this.cli.options

}

比方说这里 你能够

–cwd

–debug

–port

–proxy

–require

–hot

太多太多,然而用的很少,文档上都没提,有些性能写了,用的机会很少,值得反思一下,一开始开始我的项目的时候,是不是能够不必思考这些,先实现最外围的性能,前期在缓缓的保护。

总结

这个我的项目一开始搭建了几个月,起初就没动静了。

作为晋升技术和积攒教训,学习搭建办法,还是很有意义的。

如果这个我的项目像 umi 这样的,如果自动化 router,是不是能够更好?

没有提供额定的性能,感觉一开始就须要做好产品。

正文完
 0