学习一个Vue模板项目
最开始学习Vue的时候,不建议直接使用模板,而应该自己从头写起。模板都是人写的,要坚信“人能我能”。只有自己亲自实践,才能促进自己主动思考,才能对模板、框架有深刻的理解。
在Github上看到一个Vue的项目模板,里面包含许多新的知识。仔细研究,所获甚丰。
新库
- ora:用于美观地打印正在执行的步骤,是一个控制台打印小程序
const spinner = ora('building for production...')
spinner.start()
doSomeThing(args,()=>{
spinner.stop()
})
- chalk:彩色粉笔,是一个控制台打印小程序
- rimraf:rm命令的js实现
- node-notifier:兼容多个操作系统的通知小程序,运行下列代码会在底部弹出通知
const nodeNotifier=require("node-notifier")
nodeNotifier.notify("hello world")
notify可以接受一个JSON。
notifier.notify({
title: "title",
message: "message",
subtitle: 'subtitle',
icon: path.join(__dirname, 'logo.png')
})
- jest:JavaScript中最流行的测试框架
- webpack-merge:用于合并配置,后面的配置优先级高于前面的配置。
- semver:sematic version,语义化的版本工具,用于获取库的版本号,比较库的版本号大小等
- shelljs:使用Js调用控制台命令
- portfinder:寻找未被占用的端口
使用Node编写工具
NodeJs的两大作用:
- 改变了前端,使前端走上了工具化道路
- 改变了后端,NodeJS使JS可以用来编写后端代码
使用Node编写工具常用的库上面已经提到了
- 人性化的展示:ora、chalk、node-notifier
- 操作系统命令:rimraf、shelljs
配置文件
- .babelrc:用于配置babel
- .editorconfig:用于配置编辑器对文本文件的处理方式,大部分IDE、编辑器都有相应的插件
- .eslintignore .eslintrc.js
- .gitkeep:git是不允许提交一个空的目录到版本库上的,可以在空的文件夹里面建立一个.gitkeep文件,然后提交去即可。其实在git中 .gitkeep 就是一个占位符。可以用其他 比如 .nofile等文件作为占位符。
- package.json
{
...
"scripts":{
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"unit": "jest --config test/unit/jest.conf.js --coverage",
"test": "npm run unit",
"lint": "eslint --ext .js,.vue src test/unit",
"build": "node build/build.js"
}
...
}
从source-map说起
source-map解决了SPA程序难以定位错误代码行数的问题。
如果是我第一个采用SPA方式开发程序,我很可能会因为难以定位错误代码行数而放弃SPA技术方案。但现在人们已经通过source-map的方式解决了这个问题。
Vue框架的工具链包括Chrome插件、Vue-Loader、IDE的Vue插件、Vue-Cli等,这些工具链造就了Vue的易用性。
任何好库必然都有一个优秀的工具链,工具链能够促进库的推广。
适当地学习常用系统的插件开发是制作工具的必备常识,要掌握VSCode、Chrome、Intellij Idea等系统的插件开发。
使用NodeJS检查版本
check-version.js文件用于检测系统环境,即检测node和npm的版本是否满足要求。
一个好的应用应该主动检测环境,否则一旦出错,就会无从下手。
check-version.js
const chalk = require("chalk")
const semver = require("semver")
const packageConfig = require("../package.json")
const shell = require("shelljs")
function exec(cmd) {
return require("child_process")
.execSync(cmd)
.toString()
.trim()
}
const versionRequirements = [
{
name: "node",
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which("npm")) {
versionRequirements.push({
name: "npm",
currentVersion: exec("npm --version"),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function() {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ": " + chalk.red(mod.currentVersion) + " should be " + chalk.green(mod.versionRequirement))
}
}
if (warnings.length) {
console.log(chalk.yellow("To use this template, you must update following to modules:"))
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.log(" " + warning)
}
console.log()
process.exit(1)
}
}
三种模式
mode有三种模式:
- 开发环境:development
- 测试环境:test
- 生产环境:production
测试环境和开发环境的配置几乎是完全一样的,生产环境在打包方面进行了许多优化,使得打包文件更小。
执行构建过程,打包生产环境工具
当执行npm run build时,mode为production。此命令执行以下步骤:
- 调用那个check-versions.js,检查node和npm的版本
- 删除目标目录(默认为dist)中的全部文件,防止旧文件影响新文件
- 调用webpack库根据webpack.prod.conf.js进行打包
build.js
require('./check-versions')()
process.env.NODE_ENV = 'production'
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')
const spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
chunks: false,
chunkModules: false
}) + '\n\n')
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})
在这段代码中,需要掌握在JS中调用webpack,webpack第一个参数为配置对象,第二个参数为回调函数,回调函数包括两个参数(err,stats)。
require的用法
在NodeJS中引入其它模块时,使用const xxx=require("moduleName")
的写法。注意如果引入的模块是js文件,不要带文件后缀名.js。
- 每个文件中的exports默认是一个字典,可以直接使用exports.xxx=...的方式往上面挂东西,但是不能exports=...来改变exports的值,因为改变之后module.exports不再等于exports
console.log(global.exports)//undifined
console.log(exports)//{}
console.log(module.exports)//{}
exports = "haha"
console.log(exports)//"haha"
console.log(module.exports)//{}
- require可以导入JSON文件,如
const haha=require("./haha.json")
//等价于
const haha=JSON.parse(fs.readFileSync(path.join(__dirname,'haha.json')).toString("utf8"))
自动生成CSS的webpack配置
写CSS有很多选择:css、less、sass、scss、stylus、postcss。如果为这些语言都配置loader,webpack的配置会显得非常冗长。webpack配置本质就是JSON,我们可以使用JavaScript来生成JSON。
const cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
const output = []
const loaders = cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
在配置VueLoader时,不需要指明less的loader。VueLoader会根据webpack配置的规则寻找loader,就好像Vue中的CSS部分就像单个文件一样。
webpack基本配置
node选项
打包时,没必要把一些包打包进去。
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: "empty",
fs: "empty",
net: "empty",
tls: "empty",
child_process: "empty"
}
context选项
设置webpack寻找文件的根目录,这样就不用每个路径都是用path.join(__dirname,xxxx)
这种复杂写法了。context选项的值必须是绝对路径,entry 和 module.rules.loader选项相对于此路径进行解析。不建议使用context,因为它语义不够明确,不如手动路径拼接来得清晰。
context: path.resolve(__dirname, "../"),
resolve
- alias用于控制引入的包名所对应的真实包,
vue$
表示精确匹配vue。 - descriptionFiles默认为package.json,表示每个包的描述文件
- enforceExtension表示
require('xxxx')
中是否强制要求带后缀名,默认为false - resolve.extensions表示处理导入时可以忽略后缀名的文件,默认为['.js']。在使用ElementUI时,必须把.vue加上,否则ElementUI中的某个库会报错。
resolve: {
extensions: [".js", ".vue", ".json"],
alias: {
vue$: "vue/dist/vue.esm.js",
"@": path.join(__dirname, "../src")
}
}
module.rules
rules配置主要包括vue配置vue-loader,js配置babel-loader,各种类型的资源文件配置url-loader。这是一份基本配置,更多配置需要在开发模式和生产模式中补充。
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: "babel-loader",
include: [path.join(__dirname,"src"), path.join(__dirname,"test"), path.join(__dirname,"node_modules/webpack-dev-server/client")]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: "url-loader",
options: {
limit: 10000,
name: utils.assetsPath("img/[name].[hash:7].[ext]")
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: "url-loader",
options: {
limit: 10000,
name: utils.assetsPath("media/[name].[hash:7].[ext]")
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: "url-loader",
options: {
limit: 10000,
name: utils.assetsPath("fonts/[name].[hash:7].[ext]")
}
}
]
}
开发模式下的webpack
动态端口号
开发模式的webpack配置文件名为webpack.dev.conf.js。webpack编译时不一定接受一个JSON对象,也可以接受一个Promise对象。在开发模式下module.exports就是一个Promise对象。使用Promise的原因是,开发服务器的端口号需要使用portfinder动态确定。
下面代码中的devWebpackConfig就是开发模式下的基本配置。
webpack.dev.conf.js
const portfinder = require('portfinder')
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})
开发模式下的插件
webpack提供了丰富的插件,每个插件都有不同的配置,了解常用插件是很有必要的,但是许多插件又是可有可无的。
plugins: [
new webpack.DefinePlugin({
//config/dev.env中定义了一些常量
'process.env': require('../config/dev.env')
}),
//模块热替换插件
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // NMP shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]
DifinePlugin我们完全可以手动实现,过程也不复杂,我认为自己实现可读性、灵活性更好,没必要定义成一个插件。
HotModuleReplacementPlugin、NamedModulesPlugin、NoEmitOnErrorsPlugin这些插件用来在浏览器端或者在控制台下打印更规整信息,便于调试。
HtmlWebpackPlugin这个插件更是没有必要,它提供的功能太简单了。
CopyWebpackPlugin这个插件非常有用,当需要把静态文件原封不动地复制到dist目录时,使用这个插件。
如果index.html在dist目录中,那么我们就需要使用CopyWebpackPlugin把静态文件都放到dist目录中。开发时devServer需要配置contentBase='dist',从而指明index.html的位置。
开发模式下的配置
开发模式下的配置在基本配置的基础上略加修改。
const utils = require("./utils")
const config = require("../config")
const merge = require("webpack-merge")
const path = require("path")
const baseWebpackConfig = require("./webpack.base.conf")
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: "warning",
historyApiFallback: {
rewrites: [{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, "index.html") }]
},
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay ? { warnings: false, errors: true } : false,
publicPath: config.dev.assetsPublicPath,
proxy: {
'/api': {
target: 'http://localhost:8081',
// secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
pathRewrite: {
'^/api': ''
}
}
}
}
})
生产模式下的webpack配置
把CSS单独抽离出去
module: {
rules: utils.styleLoaders({
sourceMap: false,
extract: true,
usePostCSS: true
})
}
在plugin中
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true,
})
优化CSS,去掉不同组件之间的重复CSS
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
})
使用多个chunk
输出的文件名需要带上chunkhash
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
}
需要使用CommonsChunkWebpackPlugin
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
})
使用HtmlWebpackPlugin
HtmlWebpackPlugin并非一无是处,对于多个entry的webpack项目,它能够为每个入口生成index.html
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
})
CompressionWebpackPlugin
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' + ['js', 'css'].join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
Vue项目压缩的几种方法
优化前端项目有如下几个出发点:
- 减小体积:上策
- 减少请求次数:多次请求小文件是很浪费资源的
- 懒加载:不要一次性加载太多东西
- 使用CDN
具体措施:
- 使用bootcdn上的Vue、ElementUI,不要把大库打包进最终包里面
- 使用commons-chunk分块加载,不要一次性加载全部组件
- 使用ExtractTextPlugin把CSS分离出去,单独加载CSS
- 启用GZIP压缩
- 减少网络请求次数,如:把较小的资源文件使用base64编码
- 去掉source-map
- 使用正确的Vue版本,使用压缩版的Vue。如果使用SPA,不需要使用模板编译模块。
学习一个Vue模板项目的更多相关文章
- 【vue】创建一个vue前端项目,编译,发布
npm: Nodejs下的包管理器. webpack: 它主要的用途是通过CommonJS的语法把所有浏览器端需要发布的静态资源做相应的准备,比如资源的合并和打包. vue-cli: 用户生成Vue工 ...
- webpack 打包和手动创建一个vue的项目
首先我们为啥要用webpack,为啥不用其他的打包的工具. 先听我捋捋, Webpack有人也称之为 模块打包机 ,由此也可以看出Webpack更侧重于模块打包,当然我们可以把开发中的所有资源(图片. ...
- 安装Vue和创建一个Vue脚手架项目
首先 安装node.js,安装成功可以在控制台输入[node --version ]查看node的版本,因为安装了node会自带npm所以我们可以用 [npm --version]查到npm版本 如 ...
- Windows10安装node.js,vue.js以及创建第一个vue.js项目
[工具官网] Node.js : http://nodejs.cn/ 淘宝NPM: https://npm.taobao.org/ 一.安装环境 1.本机系统:Windows 10 Pro(64位)2 ...
- Github Actions简单部署一个vue/react项目
大体介绍 本文对github actions部署前端项目做一个简单的总结,总体来说,我感觉用它想要部署一个前端项目,可以说非常简单,简单得令人震惊
- Vue学习笔记之vue-cli脚手架安装和webpack-simple模板项目生成
vue-cli 是一个官方发布 vue.js 项目脚手架,使用 vue-cli 可以快速创建 vue 项目. GitHub地址是:https://github.com/vuejs/vue-cli 一. ...
- 利用 vue-cli 构建一个 Vue 项目
一.项目初始构建 现在如果要构建一个 Vue 的项目,最方便的方式,莫过于使用官方的 vue-cli . 首先,咱们先来全局安装 vue-cli ,打开命令行工具,输入以下命令: $ npm inst ...
- VUE系列一:VUE入门:搭建脚手架CLI(新建自己的一个VUE项目)
一.VUE脚手架介绍 官方说明:Vue 提供了一个官方的 CLI,为单页面应用快速搭建 (SPA) 繁杂的脚手架.它为现代前端工作流提供了 batteries-included 的构建设置.只需要几分 ...
- vue之cli脚手架安装和webpack-simple模板项目生成
ue-cli 是一个官方发布 vue.js 项目脚手架,使用 vue-cli 可以快速创建 vue 项目. GitHub地址是:https://github.com/vuejs/vue-cli 一.安 ...
随机推荐
- [转]一个研究生毕业以后的人生规划[ZZ]
只有选择去国内的大公司或外企才是出路 文章转载如下: 我今年39岁了, 25岁研究生毕业,工作14年,回头看看,应该说走了不少的弯路,有一些经验和教训.现在开一个小公司,赚的钱刚够养家糊口的.看看这些 ...
- 使用Ztree新增角色和编辑角色回显
最近在项目中使用到了ztree,在回显时候费了点时间,特记录下来供下次参考. 1.新增角色使用ztree加载权限,由于权限不多,所以使用直接全部加载. 效果图: 具体涉及ztree代码: jsp中导入 ...
- 【spring基础】AOP概念与动态代理详解
一.代理模式 代理模式的英文叫做Proxy或Surrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一 ...
- Codeforces 460D Little Victor and Set(看题解)
Little Victor and Set 其他都很好求, 只有k == 3的时候很难受.. 我们找到第一个不大于l的 t, 答案为 l, 3 * t, (3 * t) ^ l 感觉好像是对的, 感觉 ...
- 6-7 树的层次遍历 uva122
非常不熟练 照着书大的 晚上尝试一下自己打 了解二叉树 用数组打 第一次: #include<bits/stdc++.h> using namespace std; bool fai ...
- maven添加插件,与maven打包
1.编译插件 添加编译器插件来告诉 Maven 使用哪个 JDK 版本是用来编译项目. 2.pom <plugin> <groupId>org.apache.maven.plu ...
- 《Gradle权威指南》--Android Gradle多项目构建
No1: Android多项目设置 目录结构: MyProject/ setting.gradle app/ build.gradle libraries/ lib1/ build.gradle li ...
- HDU 1051 Wooden Sticks 造木棍【贪心】
题目链接>>> 转载于:https://www.cnblogs.com/Action-/archive/2012/07/03/2574800.html 题目大意: 给n根木棍的长度 ...
- Get package name
public class GetPackageName { public static void main(String[] args) { GetPackageName obj = new GetP ...
- class.forName的作用?
调用该访问 返回一个以字符串指定类名的类的对象. 返回字节码,返回字节码的方式有几种: ①:这份字节码曾经被加载过已经存在java虚拟机中了直接返回. ②:java虚拟机中还没有这份字节码,用类加载器 ...