vue项目中使用webpack

744 阅读3分钟

为什么我们要使用webpack--模块化编程

早期Webpack 刚出来的时候,是为了解决低版本浏览器不支持ESM(module)模块化的问题,将各个分散的JavaScript模块合并成一个文件,同时将多个JavaScript脚本文件合并成一个文件,减少HTTP请求的数量,有助于提升页面首次加载速度。在版本三开始更是乘胜追击,引入了Loader,Plugin机制,提供了各种个性化构建的能力(babel,css合并,代码压缩等)取代了同时代的Gulp等构建工具

CommonJS 与 ESM 的差异

除了语法上的差异,另外一个差异在于ESM导入模块的变量都是强绑定,导出模块的变量一旦发生变化,对应导入的模块也会随之变化,而CommonJS中导入的模块都是值的传递和引用传递(类似于传参)

CommonJS

// a.js
let mod = 'first value'

setTimeout (() => {
  mod = 'second value'
}, 500)

module.exports = mod

// b.js
const mod = require('./b')

setTimeout (() => {
  console.log (mod)
}, 1000)

node a.js
first value

ESM

//b.mjs
export let mod = 'first value'

setTimeout (() => {
  mod = 'second value'
}, 500)

//b.mjs
import { mod } from './b.mjs'

setTimeout (() => {
  console.log (mod)
}, 1000)a
node --experimental-modules a.mjs
second value

另外,ConmmonJS的模块实现,实际是给每一个模块文件做了一层函数包裹,从而使得每一个模块获取 require/module __filename/dirname 变量,那么实际执行过程是

// b.js
(function (exports, require, module, __filename) {
    const mod = require('./a.js')
    setTimeout(() => {
        console.log(mod)
    }, 1000)
})

而ESM的模块是通过 import/export 关键词实现,没有对应的函数包裹,所以在 ESM 模块中,需要使用 import/meta 变量来获取 __dilename/__dirname import/export是ECMScript 实现的一个包含模块元数据的特定对象,主要用于存放模块的 url ,而node中只支持加载本地模块,所以 俩都是使用 file 协议

require声明前的代码是会先执行,ESM 是最后一步再去执行

import url from 'url'
import path from 'path'
// import.meta: { url: file://Users/dev/mjs/a.mjs }
const __filename = url.fileURLToPath (import.meta.url)
const __dirname = path.dirname (__filename)

加载原理

所有的模块化开发,都是从一个入口文件开始,无论是NODE还是浏览器,都会根据这个入口文件进行检索。

步骤

  • 构造,寻找并且下载所有的文件并解析成模块信息(Module Records)(包含当前模块的语法抽象树,当前模块的依赖信息)
  • 实例化,将模块记录实例化各个模块质检的import,export部分对象的都在内存中指向一起
  • 执行,将import export 内存指向的地址填上实际的值

不过,相信等到 Node.js 14 LTS 版本发布时,ESM 的支持应该就能进入稳定阶段了

vue 中webpack的配置 (链式操作语法)

// vue.config.js
const path = require('path')
const webpack = require('webpack')
const CompressionWebpackPlugin = require('compression-webpack-plugin') // gzip压缩
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i // gzip匹配文件规则

const isProd = process.env.NODE_ENV === 'production'

const port = process.env.port || process.env.npm_config_port || 9526

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

const vueConfig = {
  publicPath: './',
  outputDir: 'client',
  assetsDir: 'static',
  // runtimeCompiler: true,
  devServer: {
    port: port,
    open: false,
    overlay: {
      warnings: false,
      errors: true
    },
    https: false,
    hotOnly: false,
    proxy: {
      '/api': {
        target: 'http://xxx.xxx.xxx:xxx/',
        changeOrigin: true,
        ws: true,
        pathRewrite: {
          '^/api': '/'
        }
      }
    }
  },
  configureWebpack: {
    name: 'vue项目中使用webpack',
    plugins: [
      // 忽略/moment/locale下的所有文件
      // new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
    ],
    resolve: {
      alias: {
        '@': resolve('src'),
      }
    }
  },
  chainWebpack(config) {
    config.plugins.delete('preload') // TODO: need test
    config.plugins.delete('prefetch') // TODO: need test

    // 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()

      // set preserveWhitespace
    config.module
    .rule('vue')
    .use('vue-loader')
    .loader('vue-loader')
    .tap(options => {
      options.compilerOptions.preserveWhitespace = true
      return options
    })
    .end()

  config
    // https://webpack.js.org/configuration/devtool/#development
    .when(process.env.NODE_ENV === 'development',
      config => config.devtool('cheap-source-map')
    )

  config
    .when(process.env.NODE_ENV !== 'development',
      config => {
        config
          .plugin('ScriptExtHtmlWebpackPlugin')
          .after('html')
          .use('script-ext-html-webpack-plugin', [{
            // `runtime` must same as runtimeChunk name. default is `runtime`
            inline: /runtime\..*\.js$/
          }])
          .end()
        config
          .optimization.splitChunks({
            chunks: 'all',
            cacheGroups: {
              libs: {
                name: 'chunk-libs',
                test: /[\\/]node_modules[\\/]/,
                priority: 10,
                chunks: 'initial' // only package third parties that are initially dependent
              },
              elementUI: {
                name: 'chunk-elementUI', // split elementUI into a single package
                priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
                test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
              },
              commons: {
                name: 'chunk-commons',
                test: resolve('src/components'), // can customize your rules
                minChunks: 3, //  minimum common number
                priority: 5,
                reuseExistingChunk: true
              }
            }
          })
        config.optimization.runtimeChunk('single')
      }
    )
  },
  css: {
    loaderOptions: {
      less: {
        lessOptions: {
          modifyVars: {},
          javascriptEnabled: true,
        },
      },
    },
  },
}

if (isProd) {
  vueConfig.configureWebpack.plugins.push(
    new CompressionWebpackPlugin({
      filename: '[path].gz[query]',
      algorithm: 'gzip',
      test: productionGzipExtensions,
      threshold: 10240, // 只处理比这个值大的资源,按字节计算
      minRatio: 0.8, // 只有压缩率比这个值小的资源才会被处理
      deleteOriginalAssets: false, // 是否删除原始资源 默认false
    })
  )
}

module.exports = vueConfig

babel.config.js

const isProd = ['production', 'prod'].includes(process.env.NODE_ENV)
const plugins = []

if (isProd) {
  // 删除console.log
  plugins.push('transform-remove-console')
}

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins
}

如果你使用的是vscode 则可以配置jsconfig.js 文件

参考文献 segmentfault.com/a/119000001…

// jsconfig
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "exclude": ["node_modules", "dist"]
}