Catalog
  1. 1. 了解vue-cli原理
  2. 2. 学习如何从零搭建一个脚手架
    1. 2.1. webpack基础用法
    2. 2.2. 开始引入Vue
    3. 2.3. 实现修改热更新
    4. 2.4. 区分开发模式和生产模式
    5. 2.5. 各种loader
  3. 3. 后续补充
手把手教你从零开始写一个vue-cli

了解vue-cli原理

vue-cli为vue的脚手架,它是一个专门为单页面应用快速搭建繁杂的脚手架,它可以轻松的创建新的应用程序而且可用于自动生成vue和webpack的项目模板。

很多人在学习或者写一个Vue的项目的时候,都是上来直接使用vue-cli搭建,直接vue init webpack project或者vue create project(vue-cli3)来创建项目,但是并不知道这背后到底是怎么创建的。

其实vue-cli就是一个模板下载器,把git上用webpack配置好的脚手架模板下到我们本地了,它会询问我们是否需要vue-router、vuex、axios、eslint等,根据我们的需求来进行配置,最核心的还是webpack。git上的官方模板在这里:传送门
如下图,可以看到目录结构是不是很熟悉。。。
template

学习如何从零搭建一个脚手架

webpack基础用法

废话不多说,首先创建一个项目,初始化一个package.json

mkdir vue-simple-cli && cd vue-simple-cli
npm init -y

安装wbepack必备的包(webpack最新4+的版本需要安装cli)
创建一个webpack.config.js

npm i -D webpack webpack-cli

在根目录创建index.html引入打包后的js方便看效果,创建src文件夹存放源文件,并在下面创建一个main.js,现在我们的目录结构看起来如下图所示:
目录结构

然后开始写我们的webpack配置文件

const path = require('path')

module.exports = {
mode: 'development', // 打包模式,开发or生产,决定了压缩级别
entry: './src/main.js', // 打包入口文件
output: {
path: path.resolve(__dirname, 'dist'), // 打包输出的路径dist
filename: 'bundle.js' // 打包后的文件名称
}
}

在main.js里面随便写点东西,在index.html去引入我们打包后的js。

console.log('hello webpack')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="./dist/bundle.js"></script>
</body>
</html>

然后执行webpack命令进行打包

npx webpack

将index.html在浏览器中打开,查看控制台发现打印了hello webpack,证明第一步我们已经成功了。

开始引入Vue

安装vue模块

npm i -S vue

给index.html加一个id为app的节点供vue挂载

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./dist/bundle.js"></script>
</body>
</html>

修改main.js,引入vue模块,写一个简单的vue组件去引入,然后new一个vue实例挂在到app节点上

import Vue from 'vue/dist/vue.esm' // 引入vue的es module版本

const hello = {
template: `
<div>
<h1>{{name}}</h1>
</div>
`,
data () {
return {
name: 'Hello World'
}
}
}

new Vue({
el: '#app',
template: `
<div>
<hello></hello>
</div>
`,
components: {
hello
}
})

打开index.html看到展示Hello World,证明这一步已经完成。

实现修改热更新

上面我们每次修改源文件都需要重新打包,然后刷新浏览器很麻烦,下面我们将基于webpack-dev-server实现一个修改热更新,起一个本地服务,每次修改main.js里面内容浏览器都会自动刷新到我们最新修改的状态。

安装webpack-dev-server

npm i -D webpack-dev-server

然后在package.json的scripts里面写一个命令,简化操作.

"dev": "npx webpack-dev-server" // 加--open可以自动打开浏览器

在index.html中我们需要修改打包后文件的引入路径,由于我们使用了webpack-dev-server在本地起了一个服务,打包后的js其实是放在内存中的,相对于我们的服务器的地址,所以路径改为

现在我们执行熟悉的命令来启动我们的本地服务:

npm run dev

通过 http://localhost:8080/ 就能访问到我们的页面了,现在修改main.js里面的组件的name属性,发现浏览器自动刷新展示我们修改后的结果。

区分开发模式和生产模式

在上面一步我们可以说是简单实现了开发模式下的做法,生产模式去打包生成文件,又需要修改index.html引入的js路径岂不是很麻烦,那么有没有办法自动的去修改呢?

html-webpack-plugin刚好就帮我们实现了这个功能。

npm i -D html-webpack-plugin

修改我们的webpack.config.js配置

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
mode: 'prodcution', // 打包模式,开发or生产,决定了压缩级别
entry: './src/main.js', // 打包入口文件
output: {
path: path.resolve(__dirname, 'dist'), // 打包输出的路径dist
filename: 'bundle.min.js' // 打包后的文件名称
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}

在package.json增加打包的命令

"build": "npx webpack"

现在可以把index.html中的引入js的script删掉了,html-webpack-plugin会帮我们做这件事情。

npm run build

执行完熟悉的打包命令,发现在dist目录下把index.html也生成了,并且自动插入了一段js引入了我们打包后的js文件。

在浏览器中打开html文件发现没毛病,不过还没完,接下来才是重头戏,我们要修改webpack配置区分开发和生产的配置,根据我们执行的命令来决定使用开发模式还是生产模式。

首先新建一个config文件夹,在文件夹下面分别创建webpack.dev.conf.js和webpack.prod.conf.js。

修改我们package.json中的命令,增加环境变量的参数

"dev": "npx webpack-dev-server --env.mode=development",
"build": "npx webpack --env.mode=production"

现在webpack.config.js里面之前的代码都可以删掉了,我们改写为如下代码:

module.exports = env => {
// 接收命令添加的环境变量
console.log(env)
}

执行npm run dev或者build可以看到打印出了我们写入的环境变量
env

现在我们已经能够知道当前的是开发还是生产模式,接下来开始改写webpack.config.js,这里只需要配置开发和生产都共有的配置项就可以,然后根据判断去引入开发还是生产的配置文件,进行合并配置项。

webpack.config.js

const modeOptions = {
development: require('./config/webpack.dev.conf'),
production: require('./config/webpack.prod.conf')
}

module.exports = env => {
console.log(env)
env = env || {}
return Object.assign({
entry: './src/main.js',
}, modeOptions[env.mode])
}

webpack.dev.conf.js

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
mode: 'development',
output: {
filename: 'bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}

每次打包清理掉上次打包残留的文件还需要加一个clean-webpack-plugin

npm i -D clean-webpack-plugin

webpack.prod.conf.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
mode: 'production',
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'bundle.min.js'
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}

现在我们再来执行npm run dev和build发现是不是越来越接近vue-cli脚手架,但是还有点不一样,我们现在打包的都是js文件,正常我们vue实际项目里面都是vue文件。

各种loader

webpack本身只能识别js和json,但是它提供了各种loader插件可以将不能识别的文件解析成webpack能够识别的文件。
搭建vue脚手架我们需要如下的loader:

  • css-loader 解析css文件
  • vue-loader 解析vue文件
  • vue-html-loader 解析vue内部的template
  • vue-style-loader 解析vue内部的style,并把外部的css统一归vue管
  • vue-template-compiler 编译器,把vue内的三大块编译成html、css、js
    安装这些loader
    npm i -D css-loader vue-loader vue-html-loader vue-style-loader vue-template-compiler
    修改webpack.config.js,增加loader配置,顺便优化一下引入模块省去后缀名,设置别名。
    const path = require('path')

    const modeOptions = {
    development: require('./config/webpack.dev.conf'),
    production: require('./config/webpack.prod.conf')
    }

    module.exports = env => {
    console.log(env)
    env = env || {}
    return Object.assign({
    entry: './src/main.js',
    module: {
    rules: [
    {
    test: /\.css$/,
    use: [
    'vue-style-loader',
    'css-loader'
    ]
    },
    {
    test: /\.vue$/,
    use: 'vue-loader'
    }
    ]
    },
    resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
    'vue': 'vue/dist/vue.esm',
    '@': path.join(__dirname, 'src')
    }
    }
    }, modeOptions[env.mode])
    }
    在dev和prod配置文件中增加vue-loader
    const vueLoaderPlugin = require('vue-loader/lib/plugin')
    // 省去了部分代码,相信大家都知道怎么写
    new vueLoaderPlugin()
    在src目录下新建App.vue文件,代码如下:
    <template>
    <div id="app">
    <h1>{{title}}</h1>
    </div>
    </template>

    <script>
    export default {
    name: 'App',
    data () {
    return {
    title: 'Hello Vue'
    }
    }
    }
    </script>

    <style>
    #app {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
    }
    </style>
    修改main.js
    import Vue from 'vue' // 设置了别名简写
    import App from './App' // 省去了.vue

    new Vue({
    el: '#app',
    template: `<App/>`,ue
    components: {
    App
    }
    })

接下来就是见证奇迹的时刻,运行npm run dev发现项目跑起来了,页面显示Hello Vue,修改title属性页面也会实时刷新,执行npm run build也没毛病。

后续补充

总算大功告成,先写到这里,本文完整代码在这里:传送门,后续还想继续完善参考webpack官方文档

基本上跟着敲下来实现了上面的步骤,再去用vue-cli创建一个项目去看他的配置文件就会很清晰了。

webpack主要两大核心,loader处理不认识的文件以及对内容进行统一处理,plugin扩展功能。再就是区分不同环境所需要用到的东西,把相同配置的放在base里面。

  • 开发一般需要起本地服务、修改热更新、打包的js放在内存中的不需要配置输出路径、sourcemap来定位错误方便调试…
  • 生产环境一般需要压缩、打包后的输出路径、tree shaking把没有引用到的模块给删除掉…

官方的脚手架是通过webpack-merge插件来合并配置项的,使用了webpack.DefinePlugin来设置环境变量。
开发配置中使用了webpack.NoEmitOnErrorsPlugin来屏蔽错误,避免代码出线错误中断本地服务。
生产配置中使用了webpack.HashedModulesPlugin保持module.id稳定,打包后的文件有一串hash值,假如模块没有改变hash值也不会变化浏览器就会从缓存中读取。
copy-webpack-plugin,拷贝指定的文件夹注入到打包结果中。像statci目录下的静态资源不打包就会用这个复制到dist目录下。

webpack.ProvidePlugin自动加载模块,比如很多页面都引用到某个第三方库,不必到处 import 或 require,统一设置变量就可以了,当这个变量在别的地方使用没有被赋值时就会自动加载第三方库。

webpack.DllPlugin提前处理第三方库的打包,我们一般第三方的包不会变,每次打包都会重新处理,设置dll就能优化打包速度。

Author: 匡凡
Link: https://kuangfan.github.io/2019/12/21/webpack/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99%E4%B8%80%E4%B8%AAvue-cli/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付宝