我们知道通过
vue create -p dcloudio/uni-preset-vue my-project
可以创建小程序框架。这个是DCloud 基于vue-cli 来实现的uni-app框架。使用过的都知道,有好几个模板进行选择。如果我们有自己的项目框架,如何做成预设模板,一行命令一键生成项目框架呢?例如提供给其他团队项目框架,只需要告诉一下命令就可以了,方便不少。
这个功能是通过vue-cli create 命令, -p 参数实现的,下面我们先分析一下整个流程。
1. vue create -p dcloudio/uni-preset-vue my-project 流程
要分析清楚这个流程,肯定需要调试,分享一个笨方法。局部安装vue-cli 然后在node-module/@vue/cli目录下添加log 即可。例如:新建vue-cli-local 目录 在此目录下 执行
npm install @vue/cli
局部安装vue-cli 。我们用vscode 加载vue-cli-local 目录 在node-module/@vue/cli/bin/vue.js文件中添加一行打印:
program
.command('create <app-name>')
.description('create a new project powered by vue-cli-service')
.option('-p, --preset <presetName>', 'Skip prompts and use saved or remote preset')
.option('-d, --default', 'Skip prompts and use default preset')
.option('-i, --inlinePreset <json>', 'Skip prompts and use inline JSON string as preset')
.option('-m, --packageManager <command>', 'Use specified npm client when installing dependencies')
.option('-r, --registry <url>', 'Use specified npm registry when installing dependencies (only for npm)')
.option('-g, --git [message]', 'Force git initialization with initial commit message')
.option('-n, --no-git', 'Skip git initialization')
.option('-f, --force', 'Overwrite target directory if it exists')
.option('--merge', 'Merge target directory if it exists')
.option('-c, --clone', 'Use git clone when fetching remote preset')
.option('-x, --proxy <proxyUrl>', 'Use specified proxy when creating project')
.option('-b, --bare', 'Scaffold project without beginner instructions')
.option('--skipGetStarted', 'Skip displaying "Get started" instructions')
.action((name, options) => {
if (minimist(process.argv.slice(3))._.length > 1) {
console.log(chalk.yellow('\n Info: You provided more than one argument. The first one will be used as the app\'s name, the rest are ignored.'))
}
// --git makes commander to default git to true
if (process.argv.includes('-g') || process.argv.includes('--git')) {
options.forceGit = true
}
console.log("liubbc name: ", name, "; options: ", options)
return
require('../lib/create')(name, options)
})
在vue-cli-local目录下执行如下命令:
node node_modules/@vue/cli/bin/vue.js create -p dcloudio/uni-preset-vue my-project
就看到了加的打印:
1.1 处理preset.json
按此方法继续往下看代码:
const creator = new Creator(name, targetDir, getPromptModules())
await creator.create(options)
async create (cliOptions = {}, preset = null){
if (!preset) {
if (cliOptions.preset) {
//因为vue-create 命令 加了 -p 参数 所以走这里,进行解析预设模板
// vue create foo --preset bar
preset = await this.resolvePreset(cliOptions.preset, cliOptions.clone)
} else if (cliOptions.default) {
// vue create foo --default
preset = defaults.presets['Default (Vue 3)']
} else if (cliOptions.inlinePreset) {
// vue create foo --inlinePreset {...}
try {
preset = JSON.parse(cliOptions.inlinePreset)
} catch (e) {
error(`CLI inline preset is not valid JSON: ${cliOptions.inlinePreset}`)
exit(1)
}
} else {
preset = await this.promptAndResolvePreset()
}
}
.......
}
如代码中的注释所示加了vue create 命令加了-p 参数所以去解析预设模板。
解析预设模板代码调用如下:
resolvePreset -> loadRemotePreset -> require('download-git-repo') -> loadPresetFromDir
通过download-git-repo 库去下载远程模板,下载的模板放在一个临时文件夹中。模板必须包含preset.json文件,否则会报错退出。还检查是否包含generator.js 文件,如果包含则为preset的plugins字段再添加两个字段: _isPreset: true, prompts: true 代码如下:
module.exports = async function loadPresetFromDir (dir) {
const presetPath = path.join(dir, 'preset.json')
if (!fs.existsSync(presetPath)) {
throw new Error('remote / local preset does not contain preset.json!')
}
const preset = await fs.readJson(presetPath)
// if the preset dir contains generator.js or generator/index.js, we will inject it as a hidden
// plugin so it will be invoked by the generator.
const hasGenerator = fs.existsSync(path.join(dir, 'generator.js')) || fs.existsSync(path.join(dir, 'generator/index.js'))
if (hasGenerator) {
(preset.plugins || (preset.plugins = {}))[dir.replace(/[/]$/, '')] = {
_isPreset: true,
prompts: true
}
}
return preset
}
从字面意思看,这两个字段说明这个插件是预设模板,且有提示符供选择。
download-git-repo 支持gitlab github,这次我们预设模板放在gitee上,所以不能像
vue create -p dcloudio/uni-preset-vue my-project 这样使用。但可以使用原始URL的方式,加direct前缀,--clone选项,如下所示:
vue create -p direct:https://gitee.com/liubangbo/mp-d-tab-preset.git --clone my-project文章来源:https://www.toymoban.com/news/detail-516914.html
走到这里,我们就可以新建一个空预设模板放到gitee上,当然要包含preset.json文件,否则会报错。
我们打印看一下 dcloudio/uni-preset-vue 预设:
接着往下走:
// clone before mutating
preset = cloneDeep(preset)
// inject core service
preset.plugins['@vue/cli-service'] = Object.assign({
projectName: name
}, preset)
给preset plugins字段注入@vue/cli-service字段。注意vue-cli 有2大部分,一部分就是cli 另一部分就是cli-service,这里就不详细描述了。
接下来就是生成就是创建项目目录,并生成package.json
// generate package.json with plugin dependencies
const pkg = {
name,
version: '0.1.0',
private: true,
devDependencies: {},
...resolvePkg(context)
}
const deps = Object.keys(preset.plugins)
deps.forEach(dep => {
if (preset.plugins[dep]._isPreset) {
//在这里把预设中的包含_isPreset的插件给过滤掉了
return
}
let { version } = preset.plugins[dep]
if (!version) {
if (isOfficialPlugin(dep) || dep === '@vue/cli-service' || dep === '@vue/babel-preset-env') {
version = isTestOrDebug ? `latest` : `~${latestMinor}`
} else {
version = 'latest'
}
}
pkg.devDependencies[dep] = version
})
// write package.json
await writeFileTree(context, {
'package.json': JSON.stringify(pkg, null, 2)
})
生成的package.json如下:
后面就是Initializing git repository, install 这些依赖,这样就把cli-service 给安装上了。
走到这里注意一下,下载的预设只处理了preset.json文件,并没有处理generator.js文件。
--------------------------------------------------------------------------------------------------------------------------------
下面就进入复杂的部分,开始处理generator.js了。
1.2 加载generator.js
resolvePlugins处理了generator.js,我们添加了打印,把id, 和options都打印出来看下。
// { id: options } => [{ id, apply, options }]
async resolvePlugins (rawPlugins, pkg) {
// ensure cli-service is invoked first
rawPlugins = sortObject(rawPlugins, ['@vue/cli-service'], true)
const plugins = []
for (const id of Object.keys(rawPlugins)) {
const apply = loadModule(`${id}/generator`, this.context) || (() => {})
let options = rawPlugins[id] || {}
console.log("liubbc id: ", id, "; options: ", options)
if (options.prompts) {
let pluginPrompts = loadModule(`${id}/prompts`, this.context)
if (pluginPrompts) {
const prompt = inquirer.createPromptModule()
if (typeof pluginPrompts === 'function') {
pluginPrompts = pluginPrompts(pkg, prompt)
}
if (typeof pluginPrompts.getPrompts === 'function') {
pluginPrompts = pluginPrompts.getPrompts(pkg, prompt)
}
log()
log(`${chalk.cyan(options._isPreset ? `Preset options:` : id)}`)
options = await prompt(pluginPrompts)
}
}
plugins.push({ id, apply, options })
}
return plugins
}
由于dcloudio/uni-preset-vue 预设 有prompts文件,所以会弹出提示供用户选择。我们一般只有一个项目框架,不需要prompts,这里就不分析了。
注意从代码看这里只是load generator.js 赋值给了plugins,并没有执行generator.js。
1.3 执行generator.js
看下处理过的plugins长什么样:
有3个字段,id, apply, options 。 下面就要分析一下 apply在哪里执行的。
const generator = new Generator(context, {
pkg,
plugins,
afterInvokeCbs,
afterAnyInvokeCbs
})
await generator.generate({
extractConfigFiles: preset.useConfigFiles
})
创建Generator实例,并执行generate方法。
async generate ({
extractConfigFiles = false,
checkExisting = false,
sortPackageJson = true
} = {}) {
await this.initPlugins()
// save the file system before applying plugin for comparison
const initialFiles = Object.assign({}, this.files)
// extract configs from package.json into dedicated files.
this.extractConfigFiles(extractConfigFiles, checkExisting)
// wait for file resolve
await this.resolveFiles()
// set package.json
if (sortPackageJson) {
this.sortPkg()
}
this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'
// write/update file tree to disk
await writeFileTree(this.context, this.files, initialFiles, this.filesModifyRecord)
}
async initPlugins () {
const { rootOptions, invoking } = this
const pluginIds = this.plugins.map(p => p.id)
// avoid modifying the passed afterInvokes, because we want to ignore them from other plugins
const passedAfterInvokeCbs = this.afterInvokeCbs
this.afterInvokeCbs = []
// apply hooks from all plugins to collect 'afterAnyHooks'
for (const plugin of this.allPlugins) {
const { id, apply } = plugin
const api = new GeneratorAPI(id, this, {}, rootOptions)
if (apply.hooks) {
await apply.hooks(api, {}, rootOptions, pluginIds)
}
}
// We are doing save/load to make the hook order deterministic
// save "any" hooks
const afterAnyInvokeCbsFromPlugins = this.afterAnyInvokeCbs
// reset hooks
this.afterInvokeCbs = passedAfterInvokeCbs
this.afterAnyInvokeCbs = []
this.postProcessFilesCbs = []
// apply generators from plugins
for (const plugin of this.plugins) {
const { id, apply, options } = plugin
const api = new GeneratorAPI(id, this, options, rootOptions)
await apply(api, options, rootOptions, invoking)
if (apply.hooks) {
// while we execute the entire `hooks` function,
// only the `afterInvoke` hook is respected
// because `afterAnyHooks` is already determined by the `allPlugins` loop above
await apply.hooks(api, options, rootOptions, pluginIds)
}
}
// restore "any" hooks
this.afterAnyInvokeCbs = afterAnyInvokeCbsFromPlugins
}
最终在initPlugins方法中执行了apply方法,也就是预设模板中generator.js中导出的方法。
走到这里执行generator.js的地方就找到了。分析dcloudio/uni-preset-vue (
GitHub - dcloudio/uni-preset-vue: uni-app preset for vue)可以看出,基本上就是扩展package.json中的内容,以及拷贝项目框架文件。
在这里注意3点:
1)要新建一个node 工程,在package.json中安装用到的node 库 例如,glob等。并且需要把node_modules上传
2)在放项目框架文件的时候要注意,/template/common 里面的文件是和src文件平级的
src/目录下的文件要放到/template/default/目录下
3).env .ignore文件要修改一下名称 改为 _env _ignore
我们仿照generator.js 如下所示:
const fs = require('fs')
const path = require('path')
const isBinary = require('isbinaryfile')
async function generate(dir, files, base = '', rootOptions = {}) {
const glob = require('glob')
glob.sync('**/*', {
cwd: dir,
nodir: true
}).forEach(rawPath => {
const sourcePath = path.resolve(dir, rawPath)
const filename = path.join(base, rawPath)
if (isBinary.sync(sourcePath)) {
files[filename] = fs.readFileSync(sourcePath) // return buffer
} else {
let content = fs.readFileSync(sourcePath, 'utf-8')
if (path.basename(filename) === 'manifest.json') {
content = content.replace('{{name}}', rootOptions.projectName || '')
}
if (filename.charAt(0) === '_' && filename.charAt(1) !== '_') {
files[`.${filename.slice(1)}`] = content
} else if (filename.charAt(0) === '_' && filename.charAt(1) === '_') {
files[`${filename.slice(1)}`] = content
} else {
files[filename] = content
}
}
})
}
module.exports = (api, options, rootOptions) => {
api.extendPackage(pkg => {
return {
dependencies: {
'regenerator-runtime': '^0.12.1',// 锁定版本,避免高版本在小程序中出错
'@dcloudio/uni-helper-json': '*',
"@dcloudio/uni-ui": "^1.4.19",
"core-js": "^3.6.5",
"flyio": "^0.6.2",
"uview-ui": "^2.0.31",
"vue": "^2.6.11",
"vuex": "^3.2.0"
},
devDependencies: {
"@babel/runtime": "~7.17.9",// 临时指定版本,7.13.x 会报错
'postcss-comment': '^2.0.0',
'@dcloudio/types': '^3.0.4',
'miniprogram-api-typings': '*',
'mini-types': '*',
"cross-env": "^7.0.2",
"glob": "^8.0.3",
"jest": "^25.4.0",
"open-devtools": "^0.2.1",
"sass": "^1.53.0",
"sass-loader": "^10.3.1",
"vue-template-compiler": "^2.6.11"
},
resolutions: {
"@babel/runtime": "~7.17.9"
}
}
})
api.render(async function (files) {
Object.keys(files).forEach(name => {
delete files[name]
})
const base = 'src'
await generate(path.resolve(__dirname, './template/common'), files)
await generate(path.resolve(__dirname, './template/default'), files, base, rootOptions)
})
}
最后在说一下使用方式:
vue create -p direct:https://gitee.com/liubangbo/mp-d-tab-preset.git --clone my-project
欢迎下载使用。文章来源地址https://www.toymoban.com/news/detail-516914.html
到了这里,关于模仿dcloudio/uni-preset-vue 自定义基于uni-app的小程序框架的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!