更好的观看体验移步飞书
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,是不是能够更好?
没有提供额定的性能,感觉一开始就须要做好产品。