前言

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

<!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 版本,因此并不能使用最新版本。因此需要安装的版本依赖如下:

{
  "@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(只列出了核心配置)

// 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. 入口文件

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

    /**
     * @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 等)因此我会讲他单独提取出来。

    module.exports = [
      require('./stylus'),
      require('./less.js'),
      require('./sass'),
      require('./scss'),
      require('./css'),
      require('./javascript'),
      require('./image'),
      require('./font'),
      require('./font'),
      require('./vue')
    ]

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

    // 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 文件。

    // 导入头部组件
    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组件库

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

示例

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