一、前言
最近忙着去搭建一个商城项目,已经有一个多月没写文章。然后因为我们商城是用nuxt去写的,刚好对nuxt这种约定式的写法很感兴趣,就去看了看nuxt源码,也方便日后有bug好排查
我目前计划是先写两篇文章,第一篇就是如标题,第二篇就是服务端渲染的内容。
本文主要就是说说从nuxt dev启动项目到最后生成.nuxt文件过程中都做了哪些事情,如何进行转换的
二、目录结构和入口
我们先到github下载完nuxt源码后,可以看到目录结构如下
我们就从脚手架开始分析起,因为我们启动项目都是运行nuxt-cli定义的nuxt dev。
所以我们看看cli结构
├── command.js
├── commands // 命令相关
│ ├── build.js
│ ├── dev.js
│ ├── export.js
│ ├── generate.js
│ ├── help.js
│ ├── index.js
│ ├── serve.js
│ ├── start.js
│ └── webpack.js
├── imports.js //导入相关。就是导入上面的builder、webpack等文件
├── index.js
├── list.js
├── options
│ ├── common.js
│ ├── index.js
│ ├── locking.js
│ └── server.js
├── run.js //运行命令相关
├── setup.js
└── utils // 工具
├── banner.js
├── config.js
├── constants.js
├── dependencies.js
├── dir.js
├── formatting.js
├── generate.js
├── index.js
├── memory.js
├── serve.js
└── webpack.js
于是我们到src/run.js文件中,里面写着关于我们运行nuxt dev等命令的入口
export default async function run (_argv, hooks = {}) {
const dupPkg = pkgName === '@nuxt/cli-edge' ? 'cli' : 'cli-edge'
const dupPkgJSON = resolve(__dirname, '../..' /* dist/../.. */, dupPkg, 'package.json')
// Read from process.argv
const argv = _argv ? Array.from(_argv) : process.argv.slice(2)
// Check for internal command
let cmd = await getCommand(argv[0])
// Matching `nuxt` or `nuxt [dir]` or `nuxt -*` for `nuxt dev` shortcut
if (!cmd && (!argv[0] || argv[0][0] === '-' || isNuxtDir(argv[0]))) {
argv.unshift('dev')
cmd = await getCommand('dev')
}
// Check for dev
const dev = argv[0] === 'dev'
// Try internal command
if (cmd) {
return NuxtCommand.run(cmd, argv.slice(1), hooks)
}
}
分析:
- cli-edge 就是beta版本,我们这里大部分都是默认cli 版本
- argv[0] 就是对应我们package.json中执行的命令 nuxt dev、nuxt build、nuxt start、nuxt generate 中的dev、build、start、generate 等等
- getCommand 就可以根据指定的命令加载对应的文件,并且执行NuxtCommand.run方法
关于getCommand 就在src/command/index文件中
const commands = {
start: () => import('./start'),
serve: () => import('./serve'),
dev: () => import('./dev'),
build: () => import('./build'),
generate: () => import('./generate'),
export: () => import('./export'),
webpack: () => import('./webpack'),
help: () => import('./help')
}
export default function getCommand (name) {
if (!commands[name]) {
return Promise.resolve(null)
}
return commands[name]().then(m => m.default)
}
于是拿到了对应的命令的内容,就可以执行NuxtCommand.run进行编译了。
接下来我们来看看NuxtCommand类中都有什么内容
三、NuxtCommand
import Hookable from 'hable'
export default class NuxtCommand extends Hookable {
constructor (cmd = { name: '', usage: '', description: '' }, argv = process.argv.slice(2), hooks = {}) {
super(consola)
this.addHooks(hooks)
if (!cmd.options) {
cmd.options = {}
}
this.cmd = cmd
this._argv = Array.from(argv)
}
async run () {
//...
try {
await this.cmd.run(this)
} catch (e) {
cmdError = e
}
}
}
分析:
- 能看到NuxtCommand继承了hable,hable主要提供了一些钩子函数。addHooks就是其方法,添加钩子
- 而NuxtCommand的关键方法run 其实就是执行通过getCommand引入的文件中的run方法
- 所以可以把NuxtCommand看成一个公共的管理钩子函数的中心,run方法其实就调用各个命令中自己的run方法
四、dev的详细内容
因为我们分析的是nuxt dev的情况,所以我们就到对应的文件中去 cli/src/commands/dev.js
先看看大概的内容
export default {
name: 'dev',
description:
'Start the application in development mode (e.g. hot-code reloading, error reporting)',
usage: 'dev <dir>',
options: {
...common,
...server,
open: {
alias: 'o',
type: 'boolean',
description: 'Opens the server listeners url in the default browser'
}
},
async run (cmd) {
},
async startDev (cmd, argv) {
},
async _listenDev (cmd, argv) {
},
async _buildDev (cmd, argv, nuxt) {
},
logChanged ({ event, path }) {
},
async onWatchRestart ({ event, path }, { nuxt, cmd, argv }) {
},
onBundlerChange (path) {
}
}
分析:
-
可以看到dev文件向外暴露了一些属性和方法,这些方法在后续执行的过程都会被调用到,到时候再说吗,就先不展开了
-
我们重点看看dev中的options都有哪些自带的默认属性:
-
找到common的文件位于 cli/src/options/common.js中可以看到
export default { spa: { alias: 's', type: 'boolean', description: 'Launch in SPA mode' }, universal: { alias: 'u', type: 'boolean', description: 'Launch in Universal mode (default)' }, 'config-file': { alias: 'c', type: 'string', default: defaultNuxtConfigFile, description: `Path to Nuxt config file (default: \`${defaultNuxtConfigFile}\`)` }, modern: { alias: 'm', type: 'string', description: 'Build/Start app for modern browsers, e.g. server, client and false', prepare (cmd, options, argv) { if (argv.modern !== undefined) { options.modern = normalizeArg(argv.modern) } } }, target: { alias: 't', type: 'string', description: 'Build/start app for a different target, e.g. server, serverless and static', prepare (cmd, options, argv) { if (argv.target) { options.target = argv.target } } }, 'force-exit': { type: 'boolean', default (cmd) { return ['build', 'generate', 'export'].includes(cmd.name) }, description: 'Whether Nuxt should force exit after the command has finished' }, version: { alias: 'v', type: 'boolean', description: 'Display the Nuxt version' }, help: { alias: 'h', type: 'boolean', description: 'Display this message' }, processenv: { type: 'boolean', default: true, description: 'Disable reading from `process.env` and updating it with dotenv' }, dotenv: { type: 'string', default: '.env', description: 'Specify path to dotenv file (default: `.env`). Use `false` to disable' } }
可以看到很多官网熟悉的内容:
-
spa 是否开始spa
-
config-file 的默认值就是nuxt.config,也就是我们文件中的nuxt.config.js
-
modern ,默认值是false
"scripts": { "build:modern": "nuxt build --modern=server", "start:modern": "nuxt start --modern=server" }
-
target, 默认是server用于服务器端渲染,还有static用于静态站点
-
force-exit 就是运行命令后需要退出程序的,有'build', 'generate', 'export’
-
processenv 就是是否能通过 process.env 去读取环境变量,这也是项目中经常用到的
-
dotenv 就是我们定义的环境变量文件,这里默认指向.env文件
-
-
4.1 执行run方法
在看到所有初始化的值后,我们就可以开始分析dev中run都执行了什么?
async run (cmd) {
const { argv } = cmd
await this.startDev(cmd, argv, argv.open)
},
async startDev (cmd, argv) {
let nuxt
try {
nuxt = await this._listenDev(cmd, argv)
} catch (error) {
consola.fatal(error)
return
}
try {
await this._buildDev(cmd, argv, nuxt)
} catch (error) {
}
return nuxt
},
整个run结构很清晰,做了两件事_listenDev
开始去获取Nuxt类然后交给_buildDev去打包编译。
五、_listenDev 生成Nuxt类实例
async _listenDev (cmd, argv) {
const config = await cmd.getNuxtConfig({ dev: true, _build: true })
const nuxt = await cmd.getNuxt(config)
//...
// Wait for nuxt to be ready
await nuxt.ready()
// Start listening
await nuxt.server.listen()
// Show banner when listening
showBanner(nuxt, false)
// Opens the server listeners url in the default browser (only once)
if (argv.open) {
argv.open = false
const openerPromises = nuxt.server.listeners.map(listener => opener(listener.url))
await Promise.all(openerPromises)
}
// Return instance
return nuxt
},
我们分别从几个方法去研究它
5.1 getNuxtConfig 获取配置文件
async getNuxtConfig (extraOptions = {}) {
// Flag to indicate nuxt is running with CLI (not programmatic)
extraOptions._cli = true
const context = {
command: this.cmd.name,
dev: !!extraOptions.dev
}
const config = await loadNuxtConfig(this.argv, context)
const options = Object.assign(config, extraOptions)
for (const name of Object.keys(this.cmd.options)) {
this.cmd.options[name].prepare && this.cmd.options[name].prepare(this, options, this.argv)
}
await this.callHook('config', options)
return options
}
能看到主要是通过loadNuxtConfig去拿到options值,然后如果属性中有prepare预加载就执行对应的函数
(1)loadNuxtConfig
export async function loadNuxtConfig (argv, configContext) {
const rootDir = path.resolve(argv._[0] || '.')
const configFile = argv['config-file'] //前面提到过这里config-file默认值就是nuxt.config
// Load config
const options = await _loadNuxtConfig({
rootDir,
configFile,
configContext,
envConfig: {
dotenv: argv.dotenv === 'false' ? false : argv.dotenv,
env: argv.processenv ? process.env : {}
}
})
if (argv.spa === true) {
options.ssr = false
} else if (argv.universal === true) {
options.ssr = true
}
// Server options
options.server = defu({
port: argv.port || null,
host: argv.hostname || null,
socket: argv['unix-socket'] || null
}, options.server || {}, getDefaultNuxtConfig().server)
return options
}
分析:
- 这里在通过_loadNuxtConfig去拿到nuxt.config.js里的文件内容后,看用户有没有开启spa,如果有就关闭ssr(也就是我们一开始创建项目的时候会让你选择是否启用ssr)
- 然后server就是服务端的一些初始化的值,比如端口号等就不展开了
_loadNuxtConfig
export async function _loadNuxtConfig ({
rootDir = '.',
envConfig = {},
configFile = defaultNuxtConfigFile,
configContext = {},
configOverrides = {}
} = {}) {
// ..
rootDir = path.resolve(rootDir)
const _require = createRequire(rootDir, true)
let options = {}
try {
configFile = _require.resolve(path.resolve(rootDir, configFile))
} catch (e) {
}
if (configFile) {
// Clear cache
clearRequireCache(configFile)
options = _require(configFile) || {}
if (options.default) {
options = options.default
}
// Don't mutate options export
options = { ...options }
//...
}
// Load env to options._env
return options
}
分析:
- 这里我删减了一些默认配置,比如env的默认配置,_nuxtConfigFile等等。这里只关注nuxt.config.js
- 因为我们知道require是有缓存(第一次加载后会把模块中的loaded属性设置为true,再访问的时候就不会再下载了),所以通过clearRequireCache去清空缓存,这样每次都能拿到最新的nuxt.config.js里的值
看看最终options返回的结果吧:
都是一些默认值和我们nuxt.config.js的内容
5.2 Nuxt
async getNuxt (options) {
const { Nuxt } = await core();
const nuxt = new Nuxt(options);
await nuxt.ready();
return nuxt
}
所以关键在于Nuxt类都有什么
export default class Nuxt extends Hookable {
constructor (options = {}) {
super(consola)
this.options = getNuxtConfig(options)
this.moduleContainer = new ModuleContainer(this)
// Call ready
if (this.options._ready !== false) {
this.ready().catch((err) => {
consola.fatal(err)
})
}
}
static get version () {
}
ready () {
}
async _init () {
}
_initServer () {
// ..
utils.defineAlias(this, this.server, ['renderRoute', 'renderAndGetWindow', 'listen']);
}
}
注意一下:
- _initServer方法中有'renderRoute', 'renderAndGetWindow', ‘listen’ 三个方法,这是服务端渲染的关键方法(也是我们下一篇文章讲解的关键点),最终也会挂载在Nuxt上
可以看到new Nuxt会去执行 ready方法
ready () {
if (!this._ready) {
this._ready = this._init()
}
return this._ready
}
async _init () {
if (this._initCalled) {
return this
}
this._initCalled = true
// Await for modules
await this.moduleContainer.ready()
// Await for server to be ready
if (this.server) {
await this.server.ready()
}
return this
}
分析:
- 其中的server.ready()是涉及到服务端渲染的内容,就不在这里展开。可以看下一篇文章
- moduleContainer.ready()主要就是去调用了ModuleContainer中的ready方法,去解析各种模块
(1)ModuleContainer 添加各种模块
export default class ModuleContainer {
constructor (nuxt) {
this.nuxt = nuxt
this.options = nuxt.options
this.requiredModules = {}
}
async ready () {
if (this.options.buildModules && !this.options._start) {
// Load every devModule in sequence
await sequence(this.options.buildModules, this.addModule)
}
// Load every module in sequence
await sequence(this.options.modules, this.addModule)
// Load ah-hoc modules last
await sequence(this.options._modules, this.addModule)
}
addVendor () {
}
addTemplate (template) {
}
addPlugin (template) {
}
addLayout (template, name) {
}
addErrorLayout (dst) {
}
addServerMiddleware (middleware) {
}
extendBuild (fn) {
}
extendRoutes (fn) {
}
requireModule (moduleOpts, { paths } = {}) {
}
async addModule (moduleOpts, arg2, arg3) {
}
}
分析:
-
sequence 就是通过reduce不断去解析模块里的内容,也就是nuxt.config.js中module模块里的东西
-
这里我在nuxt.config.js里写入的模块有
modules: [ '@nuxtjs/style-resources', '@nuxtjs/axios', 'cookie-universal-nuxt' ],
-
所以在这一步就是去解析nuxt.config.js 中的modules内容
-
而this.options._modules就是默认的一些模块就不展开了
-
然后可以看到ModuleContainer类就是主要用来添加各种版本Vendor,模版Template,插件plugin,中间件Middleware,布局Layout,错误布局ErrorLayout,服务端的中间件ServerMiddleware,然后扩展一些编译的内容和路由
到此整个Nuxt 类就初始化完成,它是解析整个nuxt项目的关键,包含了ModuleContainer 添加各种模块,render相关的各种方法去进行服务端渲染,listen去监听,以及一些hook
5.3 showBanner
showBanner(nuxt, false)
就是我们启动项目出现的窗口
如果我们把值设置为true,就能看到内存
最后把nuxt实例返回出去,到此整个初始化的过程就完成
六、_buildDev 生成.nuxt文件
async _buildDev (cmd, argv, nuxt) {
// Create builder instance
const builder = await cmd.getBuilder(nuxt)
// Start Build
await builder.build()
// Print memory usage
showMemoryUsage()
// Display server urls after the build
for (const listener of nuxt.server.listeners) {
consola.info(chalk.bold('Listening on: ') + listener.url)
}
// Return instance
return nuxt
},
分析:
- 看起来也很清晰就是先获取到builder
- 然后通过builder.build()编译
6.1 BundleBuilder
async getBuilder (nuxt) {
const { Builder } = await builder();
const { BundleBuilder } = await webpack();
return new Builder(nuxt, BundleBuilder)
}
我们先看看BundleBuilder这其实也就是WebpackBundler,里面主要是关于webpack编译相关的内容
export class WebpackBundler {
constructor (buildContext) {
this.buildContext = buildContext
// Class fields
this.compilers = []
this.compilersWatching = []
this.devMiddleware = {}
this.hotMiddleware = {}
// Bind middleware to self
this.middleware = this.middleware.bind(this)
// Initialize shared MFS for dev
if (this.buildContext.options.dev) {
this.mfs = new AsyncMFS()
}
}
getWebpackConfig (name) {
}
async build () {
}
async webpackCompile (compiler) {
}
async webpackDev (compiler) {
}
async middleware (req, res, next) {
}
async unwatch () {
}
async close () {
}
forGenerate () {
}
}
分析:
-
主要就是build和webpackCompile 进行编译
-
AsyncMFS类就是操作文件的方法,看看下图
6.2 Builder
export default class Builder {
constructor (nuxt, bundleBuilder) {
this.nuxt = nuxt
this.plugins = []
this.options = nuxt.options
this.supportedExtensions = ['vue', 'js', ...(this.options.build.additionalExtensions || [])] // 支持的扩展 'vue', 'js', 'ts', 'tsx’
this._buildStatus = STATUS.INITIAL
// Resolve template
this.template = this.options.build.template || VueAppTemplate
if (typeof this.template === 'string') {
this.template = this.nuxt.resolver.requireModule(this.template).template
}
// Create a new bundle builder
this.bundleBuilder = this.getBundleBuilder(bundleBuilder) //就是BundleBuilder类
}
getBundleBuilder (BundleBuilder) {
}
forGenerate () {
}
async build () {
}
// Check if pages dir exists and warn if not
async validatePages () {
}
globPathWithExtensions (path) {
}
createTemplateContext () {
}
async generateRoutesAndFiles () {
}
async normalizePlugins () {
}
async resolveFiles (dir, cwd = this.options.srcDir) {
}
async resolveRelative (dir) {
}
async resolveLayouts ({ templateVars, templateFiles }) {
}
async resolveRoutes ({ templateVars }) {
}
async resolveStore ({ templateVars, templateFiles }) {
}
async resolveMiddleware ({ templateVars, templateFiles }) {
}
async resolveCustomTemplates (templateContext) {
}
async resolveLoadingIndicator ({ templateFiles }) {
}
async compileTemplates (templateContext) {
}
resolvePlugins () {
}
createFileWatcher (patterns, events, listener, watcherCreatedCallback) {
}
assignWatcher (key) {
}
watchClient () {
}
serverMiddlewareHMR () {
}
watchRestart () {
}
unwatch () {
}
async close () {
}
}
我们来看看new Builder 都发生了啥:
- template 的默认值是VueAppTemplate,主要就是vue相关的文件版本好, 看看如下的默认值
- bundleBuilder 就是去new BundleBuilder,上面已经分析过了,主要是提供了一些操作文件的方法
6.3 builder.build()
因为拿到Builder实例后,我们需要进行编译,所以来看看Builder类中的build都做了什么
async build () {
// 状态 状态完成就直接返回了
if (this._buildStatus === STATUS.BUILD_DONE && this.options.dev) {
return this
}
// 正在编译就等待1s再继续重新编译
if (this._buildStatus === STATUS.BUILDING) {
await waitFor(1000)
return this.build()
}
// 更改状态
this._buildStatus = STATUS.BUILDING
// Wait for nuxt ready
await this.nuxt.ready()
// Create or empty .nuxt/, .nuxt/components and .nuxt/dist folders
await fsExtra.emptyDir(r(this.options.buildDir))
const buildDirs = [r(this.options.buildDir, 'components')]
await Promise.all(buildDirs.map(dir => fsExtra.emptyDir(dir)))
// Generate routes and interpret the template files
await this.generateRoutesAndFiles()
// Add vue-app template dir to watchers
this.options.build.watch.push(this.globPathWithExtensions(this.template.dir))
await this.resolvePlugins()
// Start bundle build: webpack, rollup, parcel...
await this.bundleBuilder.build()
// Flag to set that building is done
this._buildStatus = STATUS.BUILD_DONE
return this
}
分析:
- 这里会通过STATUS去管理状态,这一块写在注释上了
- 然后会调用之前初始化的时候Nuxt类中的ready方法,这里其实就是调用ModuleContainer中的ready方法,去解决各个模块的内容(就是nuxt.config.js中的modules里的内容),其中的server.ready()是涉及到服务端渲染的内容,就不在这里展开。可以看下一篇文章
- buildDir 值为.nuxt 也就是我们的目标文件,fsExtra.emptyDir就是为了看是否存在.nuxt文件,没有就创建,有就清空
- components这里也是同理,也创建一个components文件
- generateRoutesAndFiles和resolvePlugins,bundleBuilder.build() 内容比较多就单独抽离出来讲解
(1)generateRoutesAndFiles
async generateRoutesAndFiles () {
this.plugins = Array.from(await this.normalizePlugins());
const templateContext = this.createTemplateContext();
await Promise.all([
this.resolveLayouts(templateContext),
this.resolveRoutes(templateContext),
this.resolveStore(templateContext),
this.resolveMiddleware(templateContext)
]);
this.addOptionalTemplates(templateContext);
await this.resolveCustomTemplates(templateContext);
await this.resolveLoadingIndicator(templateContext);
await this.compileTemplates(templateContext);
consola__default['default'].success('Nuxt files generated');
}
分析:
-
plugins,拿到在nuxt.config.js定义的plugin值和一些初始化值
先看看我的nuxt.config.js中的plugin
plugins: [ '@/plugins/antd-ui', '@/plugins/lodash.ts', '@/plugins/mixin.ts', '@/assets/api/index.ts', { src: '@/plugins/axios.ts', ssr: true, }, ],
这里会做了一些处理,mode中的all就是代表是客户端和服务端都能使用的,client就是客户端使用。
0:{src: '/Users/tt/Code/CodeSource/nuxt/nuxt.js-dev/exa…component-injection/.nuxt/components/plugin.js', mode: 'all', name: 'nuxt_plugin_plugin_78da769d'} 1:{src: '/Users/tt/Code/CodeSource/nuxt/nuxt.js-dev/exa…onent-injection/.nuxt/cookie-universal-nuxt.js', mode: 'all', name: 'nuxt_plugin_cookieuniversalnuxt_2b5a824a'} 2:{src: '/Users/tt/Code/CodeSource/nuxt/nuxt.js-dev/examples/async-component-injection/.nuxt/axios.js', mode: 'all', name: 'nuxt_plugin_axios_b428359a'} 3:{src: '/Users/tt/Code/CodeSource/nuxt/nuxt.js-dev/examples/async-component-injection/plugins/antd-ui', mode: 'all', name: 'nuxt_plugin_antdui_2b2dca90'} 4:{src: '/Users/tt/Code/CodeSource/nuxt/nuxt.js-dev/exa…es/async-component-injection/plugins/lodash.ts', mode: 'all', name: 'nuxt_plugin_lodash_e3ee5ff8'} 5:{src: '/Users/tt/Code/CodeSource/nuxt/nuxt.js-dev/exa…les/async-component-injection/plugins/mixin.ts', mode: 'all', name: 'nuxt_plugin_mixin_17d0d500'} 6:{src: '/Users/tt/Code/CodeSource/nuxt/nuxt.js-dev/exa…/async-component-injection/assets/api/index.ts', mode: 'all', name: 'nuxt_plugin_index_b8984412'} 7:{src: '/Users/tt/Code/CodeSource/nuxt/nuxt.js-dev/exa…les/async-component-injection/plugins/axios.ts', mode: 'all', name: 'nuxt_plugin_axios_397e54eb'}
-
templateContext 就是能拿到模版。这也就是我们生成的.nuxt文件里各个文件。nuxt就是通过把数据都往模版里套,就生成了对应的.nuxt文件夹内容。
-
templateFiles 就是对应的模版
-
templateVars 可以理解成options
- 接下来就是比较重点进行编译的阶段我们重点看看
resolveRoutes
async resolveRoutes ({ templateVars }) {
// ..
if (this._defaultPage) {
} else if (this._nuxtPages) {
//..
for (const page of await this.resolveFiles(this.options.dir.pages)) {
const key = page.replace(ext, '')
// .vue file takes precedence over other extensions
if (/\.vue$/.test(page) || !files[key]) {
files[key] = page.replace(/(['"])/g, '\\$1')
}
}
templateVars.router.routes = createRoutes({
files: Object.values(files),
srcDir: this.options.srcDir,
pagesDir: this.options.dir.pages,
routeNameSplitter,
supportedExtensions: this.supportedExtensions,
trailingSlash
})
} else { // If user defined a custom method to create routes
}
// Make routes accessible for other modules and webpack configs
this.routes = templateVars.router.routes
}
分析:
-
这里files 就是去看我们pages里的文件,然后遍历出来,如下:
pages/_slug:'pages/_slug.vue' pages/index:'pages/index.vue'
我pages下就两个文件
-
然后我们看看从createRoutes后返回的数据结构
- 能看到从createRoutes将文件转换为路由
resolveStore、resolveMiddleware、resolveLayouts、resolveCustomTemplates 和resolveLoadingIndicator
这些都同理,都是根据文件目录转换为我们平常使用的数据结构
compileTemplates
async compileTemplates (templateContext) {
// Prepare template options
const { templateVars, templateFiles, templateOptions } = templateContext;
templateOptions.imports = {
...templateOptions.imports,
resolvePath: this.nuxt.resolver.resolvePath,
resolveAlias: this.nuxt.resolver.resolveAlias,
relativeToBuild: this.relativeToBuild
};
// Interpret and move template files to .nuxt/
await Promise.all(
templateFiles.map(async (templateFile) => {
const { src, dst, custom } = templateFile;
// Add custom templates to watcher
if (custom) {
this.options.build.watch.push(src);
}
// Render template to dst
const fileContent = await fsExtra__default['default'].readFile(src, 'utf8');
let content;
try {
const templateFunction = lodash.template(fileContent, templateOptions);
content = utils.stripWhitespace(
templateFunction({
...templateVars,
...templateFile
})
);
} catch (err) {
}
// Ensure parent dir exits and write file
const relativePath = utils.r(this.options.buildDir, dst);
await fsExtra__default['default'].outputFile(relativePath, content, 'utf8');
})
);
}
分析:
-
前面有说过templateFiles都是一些之前我们在初始化的时候导入的模版
-
这里通过Promise.all去readFile 读取fileContent内容
-
最后通过outputFile把文件输出
-
看看最后创建的内容
(2)bundleBuilder.build()
最后看看webpack都做了哪些事
async build () {
const { options } = this.buildContext;
const webpackConfigs = [
this.getWebpackConfig('Client')
];
if (options.modern) {
webpackConfigs.push(this.getWebpackConfig('Modern'));
}
if (options.build.ssr) {
webpackConfigs.push(this.getWebpackConfig('Server'));
}
// Configure compilers
this.compilers = webpackConfigs.map((config) => {
const compiler = webpack__default['default'](config);
// In dev, write files in memory FS
if (options.dev) {
compiler.outputFileSystem = this.mfs;
}
return compiler
});
// Start Builds
const runner = options.dev ? utils.parallel : utils.sequence;
await runner(this.compilers, compiler => this.webpackCompile(compiler));
}
这里就大概看看就不展开了,就是ssr里需要打包前后端两个bundle,后端渲染,前端挂载
七、总结
Nuxt 通过我们执行的nuxt dev去拿到dev标识,然后通过NuxtCommand去做统一去调度,执行run方法从而生成Nuxt类实例并且读取了我们在nuxt.config.js中定义的内容,最后再通过_buildDev去解析我们的项目的目录结构(因为我们的目录是按照nuxt约定的规则创建),进而去转为我们通用的文件,并且写入模版中。最后再编译到.nuxt文件中