前言

首先需要阐明的是本文单文件开发最终实现的结果:**在 HTML 中通过引入打包后的 js 文件,然后使用组件标签即可渲染。**例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<body>
<div id="page">
<xk-head :msg="msg">2213</xk-head>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
<script src="./dist/main.js"></script>
<script>
var app = new Vue({
el: '#page',
data: {
msg: 'Hello Vue!123'
}
})
</script>
</body>
</html>

为什么没有把 vue 打包到main.js中?

考虑到一个页面可能会用到多个组件,而多个组件可能并不是使用的一个 js 生成,因此将 VUE 使用 CDN 方式引入。为了避免引入的繁琐,样式文件也没有进行拆分。

快速开始

此部分只包含核心部分,代码可能不可以直接使用。

既然想要把vue单文件打包成 js 文件,那么核心就是webpackvue-loader

截至到目前(2021-07-04)为止,vue-loader 最新版本为 16+,但是 16 版本是针对 VUE3 的,而我所要使用的则是 VUE2 版本,因此并不能使用最新版本。因此需要安装的版本依赖如下:

1
2
3
4
5
6
7
8
9
10
11
{
"@babel/core": "^7.14.6",
"babel-loader": "8.2.2",
"babel-preset-env": "^1.7.0",
"vue-loader": "15.9.7",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.6.14",
"webpack": "5.42.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2"
}

接下来配置 webpack(只列出了核心配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
// 它会应用到普通的 `.js` 文件
// 以及 `.vue` 文件中的 `<script>` 块
{
test: /\.js$/,
loader: 'babel-loader'
},
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
}
]
},
plugins: [
// 请确保引入这个插件来施展魔法
new VueLoaderPlugin()
]
}

vue-loader15 版本后需要引入一个VueLoaderPlugin的 plugin 才可以使用。并且在 15 版本后 vue 中对应的 js 和 css 会分别对应其后缀的文件进行解析。

不出意外此时即可编译出一个 js,将其引入到 HTML 中即可实现。

优化配置

src为开发目录进行配置

webpack 配置文件

  1. 入口文件

    因为我们开发的是单文件组件,因此入口文件可能不止一个(不同类型或用途的组件),因此我们的入口最好是动态获取。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /**
    * @description: 获取入口
    */
    const fs = require('fs')
    const { parse, resolve } = require('path')
    /**
    * 传入路径获取入口文件配置
    * @date 2021-05-18
    * @param {String} path 入口js文件夹
    * @returns {Object} 入口配置对象
    */
    const getEntry = (path) => {
    const files = fs.readdirSync(path)
    const entry = {}
    files.forEach((item) => {
    const { name, ext, base } = parse(resolve(__dirname, path, item))
    // 如果ext存在,则认为是入口js文件
    if (ext) {
    entry[name] = resolve(path, base)
    }
    })
    return entry
    }

    module.exports = getEntry

    向外暴露一个函数,参数传入一个路径,该函数会返回入口文件的对象。

  2. loader

    由于 loader 包含的类型比较多(sass、scss、stylus 等)因此我会讲他单独提取出来。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = [
    require('./stylus'),
    require('./less.js'),
    require('./sass'),
    require('./scss'),
    require('./css'),
    require('./javascript'),
    require('./image'),
    require('./font'),
    require('./font'),
    require('./vue')
    ]

    通过一个入口文件,向外暴露一个 loader 的列表。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // stylus示例
    const stylus = {
    test: /\.styl(us)?$/,
    use: [
    'vue-style-loader',
    {
    loader: 'css-loader',
    options: { importLoaders: 1 }
    },
    'postcss-loader',
    'stylus-loader'
    ]
    }
    module.exports = stylus

    其他 loader 也是如此配置

  3. plugins

    plugins 由于只用到了两个,且不需要过多配置,因此个人认为无需提取。

  4. 其他

    例如modedevtool则是根据环境变量和个人需求进行配置。

组件开发

  1. 入口文件

    这里入口文件暂定为/src/components目录下的每一个 js 文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 导入头部组件
    import head from '../components/head/head.vue'

    /**
    * 全局filter
    */
    import filterInit from '../util/filter'

    filterInit(Vue)

    Vue.component(head.name, head)

    Vue.config.devtools = process.env.NODE_ENV === 'production' ? false : true
    window.Vue = Vue

问题

  1. vue-loader版本问题

    vue-loader版本 16 只支持 vue3,因此如果使用 vue2 则需要使用 15.x 版本.

  2. 组件库

    例如elementui组件库

    1
    2
    import { Button } from 'element-ui'
    Vue.component(Button.name, Button)

示例

示例仓库:https://github.com/kkfive/vue-components-init.git