一、cli 相关概念及知识
cli 是⼀种通过命令行来交互的工具应用,全称是 Command Line Interface。比较常见的就是 create-react-app,vue-cli 等,它们都能够将⼀段 js 脚本,通过封装为可执行代码的形式,进行⼀些操作。
使用 cli 之后,能快速的创建⼀些我们业务中的样板⽂件,比如快速创建一个项目内容,配置公共的 eslint、webpack 等等配置工具。
在封装这些内容之前,我们需要使用如下的几个库:
commander:命令行中的参数获取;github.com/tj/commande…inquirer:命令行中的表单(交互式);chalk:命令行中的可变颜色效果;clui:命令行中的loading效果;child_process:Node原生模块,提供一些方法让我们能够执行新的命令;
child_process 中有一些方法,比如 exec 等,exec 方法用于新建一个子进程,然后缓存它的运行结果,运行结束后调用回调函数。
我们这里可以使用 execSync,它能够执行一些我们 linux 中的命令。
commander 对命令行进行了解析,可以让我们比较方便的进行命令行参数的获取、读取和解析。
chalk 对应的是命令行文字颜色的更改。
clui 是一个命令行中展示 loading 效果的库。
二、自定义 cli 步骤
cli必须是一个可执行的脚本,cli必须得有一个命令;- 提供可执行的配置;
[options]代表可选项;<xxx>代表必填项;
- 可以根据用户在命令行中输入的内容,定制化的操作;
- 可交互的表单命令行工具;
- 基于用户的配置创建定制化的内容;
1、创建 package.json 文件
{
"name": "cli",
"version": "1.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
2、创建脚本文件 index.js
最后我们希望这个模块能创建出来可执行命令,给脚本文件头部添加标记;
脚本文件以 二进制 脚本的形式执行时,使用 node 命令去解析;
#!/usr/bin/env node
在 index.js 中输出日志:
#!/usr/bin/env node
console.log('hello cli');
3、配置 package.json 文件
bin 是 binary 的简写:代表的是可执行的二进制文件,可以把命令和脚本映射一起;
例如:在这个例子中,index.js 就是执行命令后执行的脚本文件;
{
"name": "miraclesol_cli_npm_publish",
"version": "1.0.1",
"description": "",
"bin": {
"cli": "./index.js"
},
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
4、命令行执行 npm link,进行软连接
npm link
输出日志:
D:\software\nodejs\cli -> D:\software\nodejs\node_modules\miraclesol_cli_npm_publish\index.js
D:\software\nodejs\node_modules\miraclesol_cli_npm_publish -> F:\MyProjectMaYun\日常练习\日常打卡\爪哇课程\10-vue cli\cli
如果已经存在软连接了,可以使用一下命令删除:
npm unlink
npm rm --global cli
执行 cli 命令,会输出 hello cli;
5、使用命令进行定制化操作
- 安装依赖:
yarn add commander
yarn add inquirer
commander基本用法:github.com/tj/commande…
const { program } = require('commander');
program
.version('0.1.0')
.arguments('<username> [password]')
.description('test command', {
username: 'user to login',
password: 'password for user, if required'
})
.action((username, password) => {
console.log('username:', username);
console.log('environment:', password || 'no password given');
});
inquirer基本用法:www.npmjs.com/package/inq…
var inquirer = require('inquirer');
inquirer
.prompt([
/* Pass your questions in here */
])
.then(answers => {
// Use user feedback for... whatever!!
})
.catch(error => {
if (error.isTtyError) {
// Prompt couldn't be rendered in the current environment
} else {
// Something else went wrong
}
});
- 编写定制化配置:
#!/usr/bin/env node
const path = require('path');
const { program } = require('commander');
const inquirer = require('inquirer');
const childProcess = require('child_process');
program
.arguments('<dir>')
.description('this is a directory!')
.action(dir => {
return inquirer
.prompt([
{
type: 'list',
name: 'framework',
message: 'which framework do you link?',
choices: ['node', 'vue']
}
])
.then(answers => {
console.log('resule >>> ', dir, answers); // result >>> test { framework: "vue" }
});
});
program.parse(process.argv);
- 执行
cli test:
#!/usr/bin/env node
const path = require('path');
const { program } = require('commander');
const inquirer = require('inquirer');
const childProcess = require('child_process');
program
.arguments('<dir>')
.description('this is a directory!')
.action(dir => {
return inquirer
.prompt([
{
type: 'list',
name: 'framework',
message: 'which framework do you link?',
choices: ['node', 'vue']
}
])
.then(answers => {
console.log('resule >>> ', dir, answers); // result >>> test { framework: "vue" }
// 通过 git 下载文件
// git clone xxx dir
// process.cwd():当前执行脚本文件的地址
const fulldir = path.resolve(process.cwd(), dir);
console.log('process.cwd() >>>>> ', process.cwd()); // F:\
console.log('fulldir >>>>> ', fulldir); // F:\vue-startup
// git clone 要使用 https 协议
// git clone 的第四个参数:要写入的目录位置
const command = `git clone https://gitee.com/MiracleSol/${answers.framework}-boilerplate.git ${fulldir}`;
console.log('command >>> ', command); // git clone https://gitee.com/MiracleSol/vue-boilerplate.git F:\vue-startup
// childProcess.exec:执行命令
childProcess.execSync(command);
});
});
program.parse(process.argv);
注意:执行目录必须不能含有中文!
- 用法:
- 切换到
F:\盘根目录下,执行cli vue-startup; process.cwd():指的是F:\盘;
- 切换到
6、发布 npm
- 使用
npm之前,要先切换镜像源为npm
-
查看当前
npm用户:npm whoami -
登录
npm:npm login -
发布:
npm publish -
使用:
npx 发布名 文件夹名- 使用
npx会自动下载依赖的包
- 使用
三、@vue/cli 相关使用
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:
- 通过
@vue/cli搭建交互式的项目脚手架; - 通过
@vue/vli + @vue/cli-service-global快速开始零配置原型开发; - 一个运行时依赖(
@vue/cli-service)一个丰富的官方插件集合,集成了前端生态中最好的工具; - 一套完全图形化的创建和管理
Vue.js项目的用户界面;
我们能够通过 npm install -g @vue/cli 来进行安装,安装成功之后,我们就能使用 vue 这个命令。
npm install -g @vue/cli-service-global
# 启动一个服务器
vue serve xxx.vue
# 将目标文件构建成一个生产环境的包
vue build xxx.vue
# 创建⼀个由 vue-cli-service 提供⽀持的新项目
vue create mytest
你会被提示选取⼀个 preset。你可以选默认的包含了基本的 Babel + ESLint 设置的 preset,也可以选 “手动选择特性” 来选取需要的特性。这个默认的设置非常适合快速创建⼀个新项目的原型,而手动设置则提供了更多的选项,它们是面向生产的项目更加需要的。
# 创建一个由 vue-cli-service 提供支持的新项目
vue create mytest
# 使用 UI 界面
vue ui
1、插件和预设配置插件
Vue CLI 使用了一套基于插件的架构,package.json 中依赖都是以 @vue/cli-plugin- 开头的。插件可以修改内部的 webpack 配置,也可以向 vue-cli-service 注入命令。在项目创建的过程中列出的特性,绝大部分都是通过插件来实现的。
2、预设配置
Vue CLI 预设配置是一个包含创建新项目所需的预定义选项和插件的 JSON 对象,让用户无需在命令提示中选择它们。在 vue create 过程中保存的预设配置会被放在你的 home 目录下的一个配置文件中 (~/.vuerc)。你可以通过直接编辑这个文件来调整、添加、删除保存好的配置。
这里有一个预设配置的示例:
# 在现有的项目中安装插件
vue add eslint
{
"useConfigFiles": true,
"router": true,
"vuex": true,
"cssPreprocessor": "sass",
"plugins": {
"@vue/cli-plugin-babel": {},
"@vue/cli-plugin-eslint": {
"config": "airbnb",
"lintOn": ["save", "commit"]
}
}
}
3、CLI 服务
在一个 Vue CLI 项目中,@vue/cli-service 安装了一个名为 vue-cli-service 的命令。你可以在 npm scripts 中以 vue-cli-service、或者从终端中以 ./node_modules/.bin/vue-cli-service 访问这个命令。
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
}
}
npm run serve
4、开发浏览器兼容性
一个默认的 Vue CLI 项目会使用 @vue/babel-preset-app,它通过 @babel/preset-env 和 browserslist 配置来决定项目需要的 polyfill。
默认情况下,它会把 useBuiltIns: 'usage' 传递给 @babel/preset-env,这样它会根据源代码中出现的语言特性自动检测需要的 polyfill。这确保了最终包里 polyfill 数量的最小化。然而,这也意味着如果其中一个依赖需要特殊的 polyfill,默认情况下 Babel 无法将其检测出来。
如果有依赖需要 polyfill,你有几种选择,如果该依赖基于一个目标环境不支持的 ES 版本撰写:将其添加到 vue.config.js 中的
transpileDependencies 选项。这会为该依赖同时开启语法语法转换和根据使用情况监测 polyfill。
如果该依赖交付了 ES5 代码并显式地列出了需要的 polyfill:你可以使用 @vue/babel-preset-app 的 polyfills 选项预包含所需要的 polyfill。注意 es6.promise 将被默认包含,因为现在的库依赖 Promise 是非常普遍的。
babel.config.js
// babel.config.js
module.exports = {
presets: [
[
'@vue/app',
{
polyfills: ['es6.promise', 'es6.symbol']
}
]
]
};
如果该依赖交付 ES5 代码,但使用了 ES6+ 特性且没有显式地列出需要的 polyfill (例如 Vuetify):请使用 useBuiltIns: 'entry' 然后再入口文件添加 import '@babel/polyfill'。这会根据 browserslist 目标导入所有 polyfill,这样你就不用再担心依赖的 polyfill 问题了,但是因为包含了一些没有用到的 polyfill 所以最终的包大小可能会增加。
5、现代模式
有了 Babel 我们可以兼顾所有最新的 ES2015+ 语言特性,但也意味着我们需要交付转译和 polyfill 后的包以支持旧浏览器。这些转译后的包通常都比原生的 ES2015+ 代码会更冗长,运行更慢。现如今绝大多数现代浏览器都已经支持了原生的 ES2015,所以因为要支持更老的浏览器而为它们交付笨重的代码是一种浪费。
Vue CLI 提供了一个 “现代模式” 帮你解决这个问题。以如下命令为生产环境构建:vue-cli-service build --modern
yarn build --modern
Vue CLI 会产生两个应用的版本:
- ⼀个现代版的包,面向支持
ES modules的现代浏览器; - 另一个旧版的包,面向不支持的旧浏览器;
6、HTML Preload
preload 是一个 HTML5 的新特性,是一个新的标签,对于浏览器加载来说,对于主资源 HTML 和 CSS 的优先级最高,其他资源优先级你不一。我们使用 preload 属性,可以让支持的浏览器提前加载资源,但加载时并不执行,等待需要时才进行执行。
这样做的好处就是我们可以将加载和执行分离开,同时也可以控制提前加载一些大型文件,防止使用时获取的页面闪烁。
我们就可以用来指定页面加载后很快会被用到的资源,所以在页面加载的过程中,我们希望在浏览器开始主体渲染之前尽早 preload。
默认情况下,⼀个 Vue CLI 应用会为所有初始化渲染需要的文件自动生成 preload 提示。 这些提示会被 @vue/preload-webpack-plugin 注入,并且可以通过 chainWebpack 的 config.plugin('preload') 进行修改和删除。
7、Prefetch
是⼀种 resource hint,用来告诉浏览器咋在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。
默认情况下,一个 Vue CLI 应用会为所有作为 async chunk 生成的 JavaScript 文件(通过动态 import() 按需 code splitting 的产物)自动生成 prefetch 提示。
这些提示会被 @vue/preload-webpack-plugin 注入,并且可以通过 chainWebpack 的 config.plugin('prefetch') 进行修改和删除。
举个例子:
vue.config.js
// vue.config.js
module.exports = {
chainWebpack: config => {
// 移除 prefetch 插件 config.plugins.delete('prefetch')
// 或者
// 修改它的选项:
config.plugin('prefetch').tap(options => {
options[0].fileBlacklist = options[0].fileBlacklist || [];
options[0].fileBlacklist.push(/myasyncRoute(.)+?.js$/);
return options;
});
}
};
当 prefetch 插件被禁用时,你可以通过 webpack 的内联注释手动选定要提前获取的代码区块:webpack 的运行时会在父级区块被加载之后注入 prefetch 链接。
import(/* webpackPrefetch: true */ './someAsyncComponent.vue');
8、处理静态资源
静态资源可以通过两种方式进行处理:
- 在
JavaScript被导入或在template/CSS中通过相对路径被引用。这类引用会被webpack处理。当你在JavaScript、CSS或.vue文件中使用相对路径(必须以.开头)引用一个静态资源时,该资源将会被包含进入webpack的依赖图中。在其内部,我们通过file-loader用版本哈希值和正确的公共基础路径来决定最终的文件路径,再用url-loader将小于4kb的资源内联,以减少HTTP请求的数量。 - 放置在
public目录下或通过绝对路径被引用。这类资源将会直接被拷贝,而不会经过webpack的处理。
9、CSS 相关
Vue CLI 项目天生支持 PostCSS、CSS Modules 和包含 Sass、Less、Stylus 在内的预处理器。
所有编译后的 CSS 都会通过 css-loader 来解析其中的 url() 引用,并将这些引用作为模块请求来处理。这意味着你可以根据本地的文件结构用相对路径来引用静态资源。
你可以在创建项目的时候选择预处理器(Sass/Less/Stylus)。
如果当时没有选好,内置的 webpack 仍然会被预配置为可以完成所有的处理。
10、webpack 相关
调整 webpack 配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象,该对象将会被 webpack-merge 合并入最终的 webpack 配置。
vue.config.js
// vue.config.js
module.exports = {
configureWebpack: {
plugins: [new MyAwesomeWebpackPlugin()]
}
};
11、构建目标
当你运行 vue-cli-service build 时,你可以通过 --target 选项指定不同的构建目标。应用模式是默认的模式。在这个模式中:index.html 会带有注入的资源和 resource hint 第三方库会被分到⼀个独立包以便更好的缓存,小于 4kb 的静态资源会被内联在 JavaScript 中,public 中的静态资源会被复制到输出目录中。
12、部署
如果你独立于后端部署前端应用 —— 也就是说后端暴露⼀个前端可访问的 API,然后前端实际上是纯静态应用。那么你可以将 dist 目录里构建的内容部署到任何静态文件服务器中,但要确保正确的 publicPath。