Who is it? What do you call? From where? Where are you going?

Let’s start with the soul test. Our original project was a VUe2 project generated by using Vue CLI. There were no major problems in the project as a whole. However, with the increasing number of modules, the construction speed of Vue CLI based on Webpack became slower and slower, and the development experience was poor.

Without further ado, let’s take a look at the original project structure:

├─.browserslistrc ├─.EditorConfig ├─.env. The following files are the same as ├─.env.development.local ├─.env.Pre ├─.env.Production ├─.env.si ├─.env.Test ├─.Env .Eslintrc.js Flag School ──.NPMRC Flag School ──.nvMRC Flag School ──.PrettierIgnore Flag School ──.Prettierrc.js Flag School ──.Stylelintignore Flag School ── . Stylelintrc. Js ├ ─ ─ the README. Md ├ ─ ─ Babel. Config. Js ├ ─ ─ the build / ├ ─ ─ the config/some project configuration file, ├─ dist/ Build ├─ doc/ Development Document ├─ Jsconfig.json ├─ Mock / ├─ package.json ├─ Postcss.config.js ├─ Public / ├─ ├── ├─ ├─ ├─ ├─ ├── map.lockCopy the code

Good why migrate?

The main purpose of migration is the difference in build speed, but Vite also has some other advantages.

  • Build speed

    The most significant advantage of Vite over Vue CLI is probably the build speed. Vite is based on esBuild pre-build dependencies, so it is much faster and the development experience is much better.

    Vite’s development environment and production environment builds are currently different because the development environment doesn’t need packaging because it uses native ESM directly, while the production environment packaging uses Rollup

  • Hiding technical details

    Well, there’s not much difference between vite and Vue CLI…

  • Play around, try new tools

    New tools after all, give it a try, and the Vue community is currently pushing it.

A journey of a thousand miles begins with a single step.

All things are difficult before they are easy. Since the Flag of migration has been set, we can only go ahead with it. Let’s take a brief look at the Vite official document, the document continues the Vue official document concise and clear advantages, basically a simple look on the Vite more understanding, the details are not too deep. After reading the documentation, it is easy to find some differences with the Vue CLI conventions, and for this part of the code must be changed, let’s begin the migration

First, get rid of the historical baggage and remove Vue CLI light pack

First, we remove all Vue CLI-related dependencies and configurations

  • Search for vuE-CLI keywords in package.json dependencies and remove the dependencies.

  • Change the startup script in script to the startup script corresponding to Vite

    Will be the original startup script

    {
      "scripts": {
        "dev": "vue-cli-service serve --open"."build": "vue-cli-service build --mode development",}}Copy the code

    Instead of

    {
      "scripts": {
        "dev": "vite"."build": "vite build"."serve": "vite preview",}}Copy the code

Then pick your teeth and make “minor changes.”

We talked about the differences in some of the conventions between Vite and Vue CLI, requiring some minor code changes

The entrance is different

The default Vue CLI entry is SRC /main.js, while the default Vite entry is index.html

Direct quotes from official documents:

Vite treats index.html as part of the source code and module diagram.

So we need to modify the original entrance first

The public/index. HTML

<! DOCTYPEhtml>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <meta name="description" content="">
    <meta name="Keywords" content="">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= webpackConfig.name %></title>
  </head>
  <body>
    <noscript>
      <strong>This page requires browser support (enabled) JavaScript!!</strong>
    </noscript>
    <div id="app"></div>
    <! -- built files will be auto injected -->
  </body>
</html>

Copy the code

Move to the root directory index. HTML and modify it

<! DOCTYPEhtml>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="renderer" content="webkit" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
    />
    <meta name="description" content="" />
    <meta name="Keywords" content="" />
    <link rel="icon" href="./favicon.ico" />
    <title>XXX</title>
  </head>
  <body>
    <noscript>
      <strong>This page requires browser support (enabled) JavaScript!!</strong>
    </noscript>
    <div id="app"></div>
    <! Vite will automatically parse the following js files -->
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
Copy the code

Different environment variables

Vue CLI environment variable loading and Vite environment variable loading are implemented through Dotenv, so the file naming convention is the same. But there are two differences:

  • Exposed way

    • In the Vue CLI, only NODE_ENV, BASE_URL, and variables starting with VUE_APP_ are exposed.

    • In Vite conventions can access MODE (run MODE development | production), BASE_URL, PROD (whether running in a production environment), DEV (whether in the development environment), and begin with VITE_ environment variable.

  • access

    • Vue CLI is accessed through process.env

    • Vite is accessed through import.meta.env

Because of these two differences, we need to:

  • Originally toVUE_APP_Environment variables that start with are replaced withVITE_At the beginning. Or you can modify the configuration filevite.config.js 的 envPrefixTo directly set toVUE_APP_You do not need to change the name of the original environment variable. (Configuration file creation is covered below)
  • willprocess.envUnified replacement byimport.meta.env.

Custom import type extensions (e.g.vue)

In the Vue CLI, we can import without writing the. Vue extension by default

import App from './App'
Copy the code

In Vite, however, it is not recommended (and actually configurable) to ignore custom extensions because of the impact on IDE and type support. Therefore, complete writing is required

import App from './App.vue'
Copy the code

Non-custom type extensions can be configured using the config item resolve.extensions, which default to [‘.mjs’, ‘.js’, ‘.ts’, ‘.jsx’, ‘.tsx’, ‘.json’]

Come on out, Vite

So far, with basic modifications as well as completion, we’re starting to introduce Vite.

Vite is relatively easy to install, the only thing to note is that different plug-ins are required for different Vue versions.

Install Vite

$ yarn add -D 
Copy the code

Install the Vue plug-in of the corresponding version

Vite provides first priority support for Vue:

  • Vue 3 single file component support: @vitejs/plugin-vue
  • Vue 3 JSX support: @vitejs/plugin-vue-jsx
  • Vue 2 supports underfin/ viet-plugin-vue2

Since we are using Vue2, install viet-plugin-vue2

$ yarn add -D vite-plugin-vue2
Copy the code

Create the configuration file vite.config.js in the root directory

import { defineConfig } from 'vite'
const { createVuePlugin } = require('vite-plugin-vue2')

// https://vitejs.dev/config/
export default defineConfig(() => {
  return {
    base: './',
    clearScreen: false,
    plugins: [createVuePlugin()],
    build: {
      target: 'es2015',
    },
  }
})
Copy the code

End scatter flower!!

If there are no other special requirements for your project, you should basically be running by now. However, our project is not so lucky, the journey has just begun…

Filling holes

Enable the JSX

Vite does not enable JSX by default. If you want to enable JSX support, you need to:

  1. When instantiating the Vue2 plug-in, pass {JSX: true} to turn on JSX support.

    export default defineConfig(() = > {
      return {
        plugins: [createVuePlugin({jsx:true})].// Introduce the Vue2 plug-in and enable JSX support}})Copy the code
  2. Add JSX identifiers to the components used

    <script lang="jsx">
        export default {
            render(){
                return <div>JSX Render</div>
            }
        }
    </script>
    Copy the code
  3. If it is a JS file with JSX syntax, you need to change the suffix to.jsx.

alias

In the original project we defined the resolve. Alias feature using Webpack in vue.config.js:

{
	"configureWebpack":{
		resolve: {
      alias: {
        '@': resolve('src'),
        '@api': resolve('src/api'),
        '@components': resolve('src/components'),
        '@containers': resolve('src/containers'),
        '@services': resolve('src/services'),
        '@styles': resolve('src/styles'),
        '@utils': resolve('src/utils'),

        '@@containers': resolve(
          'node_modules/web-lib/packages/web-lib-containers/lib'
        ),
        '@@components': resolve(
          'node_modules/web-lib/packages/web-lib-components/lib'
        ),
        '@@services': resolve(
          'node_modules/web-lib/packages/web-lib-services/lib'
        ),
        '@@utils': resolve('node_modules/web-lib/packages/web-lib-utils/lib'),
        '@@styles': resolve('node_modules/web-lib/packages/web-lib-styles/lib'),
      },
    },
	}
}
Copy the code

Looking at the configuration documentation for Vite, you can see that there is built-in support for Aliases, albeit via @rollup/plugin-alias, but fortunately the configuration is pretty much the same.

Therefore, add the resolve.alias to the configuration object returned in viet.config. js and copy the original configuration object.

resolve: {
      alias: {
        The '@': resolve('src'),
        '@api': resolve('src/api'),
        '@components': resolve('src/components'),
        '@containers': resolve('src/containers'),
        '@services': resolve('src/services'),
        '@styles': resolve('src/styles'),
        '@utils': resolve('src/utils'),

        '@@containers': resolve(
          'node_modules/web-lib/packages/web-lib-containers/lib'
        ),
        '@@components': resolve(
          'node_modules/web-lib/packages/web-lib-components/lib'
        ),
        '@@services': resolve(
          'node_modules/web-lib/packages/web-lib-services/lib'
        ),
        '@@utils': resolve('node_modules/web-lib/packages/web-lib-utils/lib'),
        '@@styles': resolve('node_modules/web-lib/packages/web-lib-styles/lib'),}}Copy the code

Node internal modules cannot be used in client code

Module compatibility is not handled in Vite, so some Node built-in modules will not be found in client code. If it is needed, it needs to be replaced with the corresponding browser-compatible implementation.

For example, the Path module can be replaced with the corresponding browser-compatible implementation of Path-Browserify.

The original code uses path.join to concatenate paths:

import path from 'path'

export function genPath(. paths) {
  returnpath.join(... paths) }Copy the code

In Vite, you need to modify path-Browserify to be browser-compatible

First install Path-Browserify

$ yarn add path-browserify
Copy the code

Then replace the reference directly

import path from 'path-browserify'

export function genPath(. paths) {
  returnpath.join(... paths) }Copy the code

Global CSS variables

In our original project, the configuration in vue.config.js was implemented by introducing sas-Resources-loader.

chainWebpack(config) {
	// Import global SASS resources
    const oneOfsMap = config.module.rule('scss').oneOfs.store
    oneOfsMap.forEach(item= > {
      item
        .use('sass-resources-loader')
        .loader('sass-resources-loader')
        .options({
          resources: [
            './node_modules/web-lib/packages/web-lib-styles/src/variables/index.scss'.'./src/styles/variables.scss',
          ],
        })
        .end()
    })
}
Copy the code

In Vite we can do this with css.preprocessorOptions.

css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@import './node_modules/web-lib/packages/web-lib-styles/src/variables/index.scss'; `,,}}}Copy the code

If you are careful, you may notice that we have introduced two CSS files into the original code. After the migration, we only introduced one CSS file. Reason is that in the. / SRC/styles/variables. We use the following syntax in SCSS js variables was derived

// export {bg: $bg; text_color: $text_color; header_height: $header_height; sidebar_width: $sidebar_width; header_bg: $header_bg; logo_bg: $logo_bg; menu_bg: $menu_bg; menu_text_color: $menu_text_color; menu_active_text_color: $menu_active_text_color; menu_hover: $menu_hover; }Copy the code

AdditionalData will appear in Vite if additionalData is configured:

Error: This file is already being loaded.
    ╷
  2 │           @import './src/styles/variables.scss'; │ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ╵ SRC/styles/variables. The SCSS root: in the stylesheetCopy the code

Refer to the following ISSUE:

  • Github.com/vitejs/vite…
  • Github.com/nuxt/vite/i…

At present, our solution is not to introduce, and then manually introduce where used to avoid this problem, there is no particularly good solution for the moment.

Dynamically import image resources such as PNG

In Vue CLI projects, we often need to dynamically identify static resources to import through runtime variables

const iconSrc = require(`./images/${iconName}.png`)
Copy the code

The above code uses webpack’s url-loader or url-loader and will be handled automatically

Vite handles dynamic import of image resources in the following ways:

  1. Import with import.meta.globEager glob

    This approach will fully introduce matching images

    const images = import.meta.globEager('./images/*.png') // All matching images will be imported directly
    const iconSrc = iamges[`./images/${iconName}.png`].default
    
    if(iamges[`./images/${iconName}.png`]) {// TODO
    }else{
      // TODO
    }
    Copy the code
  2. Import using new URL(URL, import.meta. URL)

    This way you can’t tell if the image doesn’t exist at the code level

    const iconSrc = new URL(`./images/${iconName}.png`.import.meta.url) // A URL instance will be returned
    Copy the code

SVG Sprite figure

In the original project we used webPack’s SVG-Sprite-loader plug-in to implement SVG Sprite diagrams.

chainWebpack(config) {
  // set svg-sprite-loader
    config.module
      .rule('svg')
      .exclude.add(resolve('src/icons'))
      .end()
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]',
      })
      .end()
}
Copy the code

Thanks to the community, in Vite we can use the plugin ** vite-plugin-SVG-icons ** to replace them to achieve the same functionality. The usage mode is basically the same. You can check the corresponding documents for specific usage.

Note: SVG-sprite-loader requires manual import of SVG files, whereas viet-plugin-SVG-icons are automatically imported.

Introduce plug-ins in the Plugins option of the Vite configuration

import viteSvgIcons from 'vite-plugin-svg-icons'

export default defineConfig(({ mode }) = > {
 return {
   viteSvgIcons({
        iconDirs: [resolve('src/icons/svg')].symbolId: 'icon-[name]',})}}Copy the code

Introduced in main.js

import 'virtual:svg-icons-register'
Copy the code

Glob import

If we used webpack’s require.context syntax in old projects, we would get an error in Vite. Instead, Vite provides import.meta.glob and import.meta.globEager

For example, modules were introduced dynamically in SRC /store/index.js

// Get the module file
const getModuleFiles = () = > require.context('./modules'.true./\.js$/)
// Get the module object list
const getModules = () = > {
  const storeFiles = getModuleFiles()

  const modules = storeFiles.keys().reduce((modules, modulePath) = > {
    // set './app.js' => 'app'
    const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/.'$1')
    const value = storeFiles(modulePath)
    modules[moduleName] = value.default
    return modules
  }, {})
  return modules
}

const modules = getModules()
Copy the code

Change to Vite

const files = import.meta.globEager('./modules/*.js')

const modules = Object.keys(files).reduce((pre, path) = > {
  pre[path.replace(/(\.\/modules\/|\.js)/g.' ')] = files[path].default
  return pre
}, {})
Copy the code

Hot update

In the original SRC /store/index.js we enabled store hot update in the following way

If (module.hot) {const modulePaths = getModuleFiles().keys() module.hot.accept(['./getters', ...[modulePaths]], () => {const getters = require('./getters'). Default const modules = getModules()  getters, modules, }) }) }Copy the code

It can be removed directly from Vite without additional configuration.

Use environment variables in the Vite configuration file

If you want to use environment variables in a Vite configuration, you can’t get them using import.meta.env because Vite parses the configuration file first and then environment variables. Therefore, manual parsing access is available only through Dotenv.

export default defineConfig(({ mode }) = > {
  // Access common environment variables
  const { PORT } = require('dotenv').config({ path: `./.env` }).parsed || {}
	
  // Access environment variables based on the running environment
  const {
    VITE_APP_URI_BUSINESS_SERVICE_BASE,
    VITE_APP_URI_FILE_SERVICE_BASE,
    VITE_APP_USER_SOCTET_BASE,
  } = require('dotenv').config({ path: `./.env.${mode}` }).parsed || {}
  return {
    server: {port: PORT,
    }
  }
})
Copy the code

A little dessert, a little engineering optimization

Husky + Lint-staged implementations commit Lint

Husky allows us to define git-hooks that can be used to inject hooks into the Git lifecycle to define our workflow, such as code validation or email notifications. Lint-staged allows us to process only submitted code. Combining the two can trigger a series of actions on newly committed code within the desired Git life cycle. The most common is to validate and format your code before a Git pre-commit.

Installation and use

  1. Install the husky

    $ yarn add -D husky 
    Copy the code
  2. Open the Git hooks

    yarn husky install
    Copy the code
  3. Automatically enable Git hooks after adding installation project dependencies

    package.json

    {
      "scripts": {
        "prepare": "husky install"}}Copy the code
  4. Install the lint – staged

    $ yarn add -D lint-staged
    Copy the code
  5. Configuration lint – staged

    package.json

    {
      "lint-staged": {
        "**/*.{vue,js}": [
          "prettier --write"."eslint --fix"."git add"]."**/*.{html,vue,css,scss}": [
          "prettier --write"."stylelint --allow-empty-input --fix"."git add"]."**/*.{md,json}": [
          "prettier --write"."git add"]}}Copy the code
  6. Configure hooks to trigger Lint-staged commits

    $ npx husky add .husky/pre-commit "npx lint-staged"
    Copy the code
  7. Add hooks to Git

    $ git add .husky/pre-commit
    Copy the code

Git supports hooks

Hook type The name of the hook trigger Hook parameters May suspend
Submit workflow Hooks pre-commit Before you enter the commit information, that is, before you generate the commit object. This is a good time to review code and run tests. \ is
Submit workflow Hooks prepare-commit-msg Run after the default information is created before starting the Submit information editor. You can modify the default commit information here. filepath commitType SHA-1 is
Submit workflow Hooks commit-msg Occurs after submission information is entered and before the submission is executed. This is where you can check the submission information for specification and modify the submission information. filepath is
Submit workflow Hooks post-commit When the entire submission is complete. Generally used for notifications, such as mail notification submissions, etc. (but it is recommended to do this in server-side post-receive hooks). \ no
Email workflow hook applypatch-msg After the patch submission information is generated and before the patch application. Can be used to check whether patch submission information is normal. mergeFilename is
Email workflow hook pre-applypatch Run after the patch is applied and before the commit object is generated. So, as with pre-COMMIT, it’s a good time to check your code and run your tests. \ is
Email workflow hook post-applypatch When the entire submission is complete. As with post-commit, this is a good time to notify. \ no
Other client-side hooks pre-rebase Running before the transition. \ is
Other client-side hooks post-rewrite Triggered by the command that replaces the submitted record. Such asgit commit --amend commandName no
Other client-side hooks post-checkout After a successful git Checkout run. commandName no
Other client-side hooks post-merge After Git Merge runs successfully. commandName no
Other client-side hooks pre-push Updated remote reference but not pushed before local commit. originBranchName HEAD is

Complete the map

Below is the migrated directory structure and the complete viet.config.js file

School exercises ──.env. School Exercises ──.env. School Exercises ──.env. School Exercises ──.env . Eslintrc. Js ├ ─ ─. Git / ├ ─ ─ the gitignore ├ ─ ─ the husky / ├ ─ ─ the NPMRC ├ ─ ─ the NVMRC ├ ─ ─ the prettierignore ├ ─ ─ the prettierrc. Js ├ ─ ─ . Stylelintignore ├ ─ ─. Stylelintrc. Js ├ ─ ─ the README. Md ├ ─ ─ _templates / ├ ─ ─ dist / ├ ─ ─ dist., tar, gz ├ ─ ─ doc / ├ ─ ─ index. The HTML ├ ─ ─ ├─ public/ ├─ SRC/Exercises / ├─ vite.config.js ├─ ├.jsconfig.json ├─ package.json ├─ public/ Exercises ├─ SRC/Exercises/Exercises ├─ vite.configCopy the code
import path from 'path'
import { defineConfig } from 'vite'
import viteSvgIcons from 'vite-plugin-svg-icons'
const { createVuePlugin } = require('vite-plugin-vue2')

function resolve(dir) {
  return path.join(__dirname, dir)
}

// https://vitejs.dev/config/
export default defineConfig(({ mode }) = > {
  const { PORT } = require('dotenv').config({ path: `./.env` }).parsed || {}

  const {
    VITE_URI_BUSINESS_SERVICE_BASE,
    VITE_URI_FILE_SERVICE_BASE,
    VITE_USER_SOCTET_BASE,
  } = require('dotenv').config({ path: `./.env.${mode}` }).parsed || {}

  return {
    base: '/'.clearScreen: false.plugins: [
      createVuePlugin({ jsx: true }),
      viteSvgIcons({
        iconDirs: [resolve('src/icons/svg')].symbolId: 'icon-[name]',})],resolve: {
      extensions: ['.mjs'.'.js'.'.ts'.'.jsx'.'.tsx'.'.json'].alias: {
        The '@': resolve('src'),
        '@api': resolve('src/api'),
        '@components': resolve('src/components'),
        '@containers': resolve('src/containers'),
        '@services': resolve('src/services'),
        '@styles': resolve('src/styles'),
        '@utils': resolve('src/utils'),

        '@@containers': resolve(
          'node_modules/web-lib/packages/web-lib-containers/lib'
        ),
        '@@components': resolve(
          'node_modules/web-lib/packages/web-lib-components/lib'
        ),
        '@@services': resolve(
          'node_modules/web-lib/packages/web-lib-services/lib'
        ),
        '@@utils': resolve('node_modules/web-lib/packages/web-lib-utils/lib'),
        '@@styles': resolve('node_modules/web-lib/packages/web-lib-styles/lib'),}},css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@import './node_modules/web-lib/packages/web-lib-styles/src/variables/index.scss'; `,}}},server: {
      port: PORT,
      open: true.proxy: {
        '/user': {
          target: VITE_URI_BUSINESS_SERVICE_BASE,
          secure: false.changeOrigin: true,},'/file': {
          target: VITE_URI_BUSINESS_SERVICE_BASE,
          secure: false.changeOrigin: true,},'^/group1': {
          target: VITE_URI_FILE_SERVICE_BASE,
          secure: false.changeOrigin: true,},// User center long link
        '^/wsUser': {
          target: VITE_USER_SOCTET_BASE,
          secure: false.changeOrigin: true.ws: true.// The actual address does not have the WSMSG prefix
          pathRewrite: {
            '^/wsUser': ' ',},// Resolve dev Server down if WS agent is disconnected
          onProxyReqWs(proxyReq, req, socket) {
            socket.on('error'.err= > {
              console.error(err)
              // console.error(options)},},},},build: {
      target: 'es2015',}}})Copy the code