vue-cli3 vue2 retain webpack support vite successful practice

hello everyone!

The text was born to improve development efficiency and experience practice.

Project background:

  • Scaffold: vue-cli3, specifically "@ Vue / cli service": "^ 3.4.1"
  • Library: vue2, specifically: "vue": "2.6.12"
  • Note: no typescript, not ssr

Pain point: with the passage of time and the continuous iteration of business, there are more and more dependencies, functions and codes. The local start-up of the project is relatively slow, and the startup and update are relatively slow.

Improvement goal: retain the original webpack and support vite. And minimize changes and maintenance costs.

consider:

  • The vite production environment uses rollup, which is more suitable for packaging libraries.
  • vite packaging did not improve the efficiency much.
  • Keep the original webpack mode to ensure the stability and security of production as much as possible.

practice

It mainly deals with three aspects:

  • The configuration file shall be based on Vue config. JS add vite config. js
  • Entry file index How HTML supports vite
  • Configure vite startup command
  • May need

    • Route lazy loading, vite needs special handling
    • Solve the introduction and mixing of commonjs and esModule

Add vite config. js

Create a vite. Net in the root directory of the project config. js

Install required dependencies

npm i vite vite-plugin-vue2 -D

According to Vue config. JS in vite config. Add corresponding configuration in JS

// If you change the logic of the file, please change Vue config. js
import path from 'path'
import fs from 'fs'
import { defineConfig } from 'vite'
import config from './config'
import { createVuePlugin } from 'vite-plugin-vue2'
import { injectHtml } from 'vite-plugin-html'

const resolve = dir => path.join(__dirname, dir)

const alias = {
  vue: 'vue/dist/vue.esm.js',
  '@': resolve('src'),
}

const publicPath = '/'
const mode = 'development'

// https://vitejs.dev/config/
export default defineConfig({
  base: publicPath,
  plugins: [
    createVuePlugin(), 
  ],
  // These variables are injected into the project. If there are variables starting with process, they need to be injected here. vite will not inject process related variables and values by default
  define: {
    'process.env.NODE_ENV': JSON.stringify(mode),
    'process.env.publicPath': JSON.stringify(publicPath),
  },
  resolve: {
    // Configure alias
    alias,
    // The list of extensions you want to omit during import must be added vue, because vite does not support omission by default vue suffix
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
  },
  server: {
    // Allow cross domain
    cors: true,
    proxy: {
      // Vue can be copied directly config. Corresponding proxy in JS
    }
  }
})

sass correlation

Special description of configuration: if sass preprocessor is useful in the project, and variables are used.

Installation required sass@1.32.13 , you cannot install a version higher than 1.32. This problem exists with the higher version Sass reports an error: Using / for division is deprecated

npm i sass@1.32.13  -D
export default defineConfig({
  ...
  css: {
    // Pass options to sass
    preprocessorOptions: {
      scss: {
        charset: false, // Only the latest version of sass is supported
        additionalData: `@import "src/sass/common/var.scss";`,
      }
    },
  },
  ...
})

Note: if the production environment is packaged with vite, use sass@1.32.13 Will encounter this problem There is a warning in vite2 packaging, "@ charset" must be the first. How to eliminate it? , but sass@1.32.13 The configuration in the document is not supported. So this can be regarded as a reason why vite is not used in production environment.

You also need to globally replace / deep / with:: v-deep

Entry file index How HTML supports vite

Due to vite's project requirements, index The HTML file is in the root directory and has an entry file configuration.

So index Move HTML from the public directory to the root directory. And join

<% if (typeof isVite === 'boolean' && isVite) { %>
  <script type="module" src="/src/main.js"></script>
<% } %>

This configuration is to enable both webpack and vite modes to support and share a file

And in order to The isVite variable is injected into the HTML file, which needs to be installed

npm i vite-plugin-html -D

Need to be in vite config. JS

...
import { injectHtml } from 'vite-plugin-html'

export default defineConfig({
  ...
  plugins: [
    ...
    injectHtml({
      data: {
        title: 'vite-plugin-html-example',
        isVite: true
      },
    }),
  ],
  define: {
    'process.env.isVite': JSON.stringify(true)
  },
  ...
})

Configure vite startup command

Finally, in package Add script in JSON

"scripts": {
  "vite-start": "vite"
}

Route lazy loading, vite needs special handling

vue implements lazy loading of routes in this way

  const Home = import(/* webpackChunkName: "[home]" */ `@/page/home.vue`)

  const routes = [
    {
      path: `/home`,
      name: 'home',
      component: Home
    },
  ]

But vite doesn't support it. It can be solved

const modules = import.meta.glob('@/page/*.vue')

const Home = modules['@/page/home.vue']
const modules = import.meta.glob('@/page/*.vue')

// vite generates code
const modules = {
  '@/page/home.vue': () => import('@/page/home.vue'),
  '@/page/page1.vue': () => import('@/page/page1.vue'),
  '@/page/page2.vue': () => import('@/page/page2.vue'),
  ...
}

reference resources: Vite glob import

So you can encapsulate:

function loadPage (view) {
  if (process.env.isVite) {
    const modules = import.meta.glob('../pages/*.vue')
    return modules[`../pages/${view}.vue`]
  }
  
  return () => import(/* webpackChunkName: "[request]" */ `@/pages/${view}`)
}

// use:
const routes = [
  {
    path: `/home`,
    name: 'home',
    component: loadPage('home'),
  },
  {
    path: `/404`,
    name: '404',
    component: loadPage('404'),
  },
]

But webpack does not support import.meta , it needs to be handled by loader. Solution: create a local file webpack import meta loader js.

// Source: https://github.com/KmjKoishi/webpack-import-meta-loader-fixed
// This is a fix to @ open WC / webpack import meta loader
// Mainly repair when this The rootcontext does not exist. Does not exist when building the production environment

/* eslint-disable */
// @ts-nocheck
const path = require('path');
function toBrowserPath(filePath, _path = path) {
  return filePath.replace(new RegExp(_path.sep === '\\' ? '\\\\' : _path.sep, 'g'), '/');
};
const regex = /import\.meta/g;

/**
 * Webpack loader to rewrite `import.meta` in modules with url data to the source code file location.
 *
 * @example
 * return import.meta;
 * // becomes: return ({ url: `${window.location.protocol}//${window.location.host}/relative/path/to/file.js` });
 *
 * return import.meta.url;
 * // becomes: return ({ url: `${window.location.protocol}//${window.location.host}/relative/path/to/file.js` }).url;
 */
module.exports = function (source) {
  const path = require('path');

  const relativePath = this.context.substring(
    this.context.indexOf(this.rootContext) + (this.rootContext && this.rootContext.length >= 0 ? (this.rootContext.length + 1) : 0),
    this.resource.lastIndexOf(path.sep) + 1,
  );

  const browserPath = toBrowserPath(relativePath);

  const fileName = this.resource.substring(this.resource.lastIndexOf(path.sep) + 1);

  let found = false;
  let rewrittenSource = source.replace(regex, () => {
    found = true;
    return `({ url: getAbsoluteUrl('${browserPath}/${fileName}') })`;
  });

  if (found) {
    return `
      function getAbsoluteUrl(relativeUrl) {
        const publicPath = __webpack_public_path__;
        let url = '';
        if (!publicPath || publicPath.indexOf('://') < 0) {
          url += window.location.protocol + '//' + window.location.host;
        }
        if (publicPath) {
          url += publicPath;
        } else {
          url += '/';
        }
        return url + relativeUrl;
      }
${rewrittenSource}`;
  } else {
    return source;
  }
};

vue.config.js modify configuration:

const resolve = dir => require('path').join(__dirname, dir)

module.exports = {
  ...
  configureWebpack: {
    ...
    module: {
      rules: {
        ...
        {
          test: /index.js$/,
          use: [
            resolve('webpack-import-meta-loader'),
            'babel-loader'
          ],
          include: [resolve('src/router')]
        }
      }
    }
  }
  ...
}

Solve the introduction and mixing of commonjs and esModule

Mixed use

In webpack mode, if there is a mixture of commonjs and esModule in the source code of your src project.

Scheme 1: do not change the source code, in vite config. JS to convert commonjs into esModule

Install npm i cjs2esmodule -D

In vite config. JS plus configuration

export default defineConfig({
  plugins: [cjs2esmVitePlugin()]
})

If this scheme can make your project run normally. Otherwise, Option II may be required.

Scheme 2: manually change the commonjs syntax in src code to esModule

introduce

If your project has a config JS, by Vue config.js. Then you may need to deal with it.

vue.config.js must be a common JS syntax file to be used, otherwise an error will be reported.

vite.config.js can use either esModule syntax or commonjs syntax. The default is esModule syntax.

If the above is mixed, you adopt scheme 2, and config. Is also used in the src code js. Then you can only put config JS to esModule. Vue config.js is not supported. The scheme adopted is based on config JS automatically generates a config CJS js.

The purpose is to reduce later maintenance costs.

// transformConfig2cjs.js

// When running the project, it will be based on config JS auto generated file: config CJS JS to make Vue config JS can be used directly
const {transformAsync} = require('@babel/core');
const plugin = require('@babel/plugin-transform-modules-commonjs')
const fs = require('fs')
const resolve = dir => require('path').join(__dirname, dir)
async function transfrom () {
  const inputPath = resolve('config.js')
  const input = fs.readFileSync(inputPath, 'utf-8')
  const {code} = await transformAsync(input, {
    sourceType: 'module',
    plugins: [ plugin ]
  });
  
  fs.writeFileSync(resolve('./config-cjs.js'), code);
}
transfrom()

Then in Vue config. config.js JS is changed to config CJS

Finally, in package Change the script in JSON and regenerate the latest configuration each time.

"scripts": {
  "transformConfig2cjs": "node transformConfig2cjs.js",
  "serve": "npm run transformConfig2cjs && vue-cli-service serve",
  "build": "npm run transformConfig2cjs && vue-cli-service build",
}

summary

Encountered a lot of pits, are grammar related, and finally all 11 solved!

Some other schemes have been tried, but they can't be used. My project doesn't take effect:

After support, the development is really efficient!

I hope this article is helpful to those in need.

Keywords: Vue.js

Added by evan12 on Thu, 23 Dec 2021 14:41:27 +0200