基于webpack4.x手动实现vue+ts打包配置 -- 基础篇

基于webpack4.x手动实现vue+ts打包配置 – 基础篇

由于vue-cli进入3.0以来,其打包的配置被隐藏到了@vue/cli-service包里,webpack配置的修改只能通过vue.config.js进行。

    "@vue/cli-plugin-babel": "^4.1.2",
    "@vue/cli-plugin-eslint": "^4.1.2",
    "@vue/cli-plugin-typescript": "^4.1.2",
    "@vue/cli-plugin-unit-jest": "^4.1.2",
    "@vue/cli-service": "^4.1.2", //vue-cli脚手架

这种模式虽然极大的简化了开发人员的项目配置,但对于想提升自我的前端来说,无疑是不太友好的。

下面我把自定义webpack配置的经验总结一下,以供参考指正:

webpack配置1
虽然webpack配置较为繁杂,但只要掌握了规律,写起配置就没难了,而且webpack经过这几年的发展也有了中文文档
基于webpack4.x手动实现vue+ts打包配置 -- 基础篇_第1张图片

总结起来有四个方面的配置:
  • 项目出入口配置
  • 文件路径解析配置
  • 文件对应的loader配置
  • webpack插件配置

下面这段(webpack.base.js)配置就是webpack打包入口/出口配置。

...... //省略了各模块的引入
module.exports = {
...
  context: path.resolve(__dirname, '../'),
  entry: './src/main.ts',    //打包的入口文件,这里使用了.ts
  output: {
    path: path.resolve(__dirname, '../dist'),       //打包后的输出文件
    filename: 'js/[name].js'//打包后的文件命名规则,这里保持文件名
    publicPath: '/'       //打包后文件的引用路径,默认为当前路径'/'
  },
  target: 'web' // 输出目标浏览器
  .......

常用的自动后缀名和路径别名解析,如下:

resolve: {
    //自动识别的文件后缀,比如写 from '@/store',会被依次解析为 '绝对路径/store.mjs/.js等'直到找到文件
  extensions: ['.mjs', '.js', '.ts', '.tsx', '.jsx', '.vue', '.json', '.wasm', '.scss'],
  alias: {
    '@': resolve('src'), //src路径别名
    vue$: 'vue/dist/vue.esm.js'
  }
},

往下是webpack最为核心的loader配置,虽然webpack的loader各种各样,但是使用起来十分简单,如这段vue + babel + ts + scss + postcss的loader配置:

module: {
  rules: [
    {
      test: /\.vue$/,  //正则匹配.vue后缀文件,使用vue-loader这个loader进行解析
      use: [{loader: 'vue-loader'}  ]
    },
    {
      test: /\.js$/, //正则匹配.js后缀文件,使用babel-loader进行解析
      exclude: /node_modules/,
      use: [{loader: 'babel-loader'} ]
    },
    {
      test: /\.tsx?$/,  //正则匹配.ts|tsx后缀文件,使用ts-loader进行解析,这里就是使用TypeScript的关键配置
      use: [
        {loader: 'babel-loader'},
        {loader: 'ts-loader',options: {transpileOnly: true,appendTsSuffixTo: ['\\.vue$'],happyPackMode: true}}]
    },
    {
      test: /\.(css|scss)$/,  //解析scss样式,同时使用了postcss对css进行浏览器兼容等处理
      use: ['vue-style-loader','css-loader','postcss-loader','sass-loader']
    }
  ]
},

这样就非常简单的配置好了vue、ts、scss的代码转换;

但需要处理一个新文件类型时,总结起来就是三步:

  1. 指定匹配文件正则test;
  2. 指定文件应该使用的loader;
  3. 对于有参数需要配置的loader,写options。

webapck的插件非常丰富,正是有了这些插件,才使得打包项目更加简单起来:

......
plugins: [ //webpack的插件配置,大部分插件都是引入即可用,无需配置
    new VueLoaderPlugin(),    //vue-loader的伴生插件
    new CleanWebpackPlugin(),   //清除./dist文件夹
    new ProgressPlugin(), // 显示打包进度
    new HtmlWebpackPlugin({     //自动注入打包后的.js文件
        filename: 'index.html',
        template: resolve('/public/index.html'),
        chunksSortMode: 'none' // 不对文件引入自动排序
  }),
    new CopyWebpackPlugin([    //直接拷贝静态文件至输出文件夹
        {
          from: './public',
          to: './public', // /dist/public
          toType: 'dir',
          ignore: ['.DS_Store', '*.html']
        }])
  ]
......

以上这些,便是webpack打包的内容了,有了这些配置,我们的vue项目已经可以完成打包了,是不是并没有想象中的复杂?

完整代码:
const path = require('path')
const webpack = require('webpack')
const ProgressPlugin = require('progress-bar-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

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

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

const inlineLimit = 4096

module.exports = {
  mode: isProduction ? 'production' : 'development',
  target: 'web',
  context: path.resolve(__dirname, '../'), //用于从配置中解析入口起点(entry point)和 loader
  entry: './src/main.ts',
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'js/[name].js',
    publicPath: '/' //所有资源请求的路径,可以为url,或者前缀,默认为''
  },
  resolve: {
    extensions: ['.mjs', '.js', '.ts', '.tsx', '.jsx', '.vue', '.json', '.wasm', '.scss'],
    alias: {
      '@': resolve('src'),
      vue$: 'vue/dist/vue.esm.js'
    },
    modules: ['node_modules', resolve('node_modules')]
  },
  module: {
    noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/,
    rules: [
      {
        test: /\.vue$/,
        use: [
          {
            loader: 'vue-loader'
          }
        ]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader'
          }
        ]
      },
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: 'babel-loader'
          },
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,
              appendTsSuffixTo: ['\\.vue$'],
              happyPackMode: true
            }
          }
        ]
      },
      {
        test: /\.(css|scss)$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'postcss-loader',
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
              //sass-loader更新到8.0.0,data=>prependData
              prependData: '@import "@/assets/scss/variable.scss";'
            }
          }
        ]
      },
      {
        test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: inlineLimit,
          fallback: {
            loader: 'file-loader',
            options: {
              name: 'img/[name].[hash:8].[ext]'
            }
          }
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/i,
        loader: 'url-loader',
        options: {
          limit: inlineLimit,
          fallback: {
            loader: 'file-loader',
            options: {
              name: 'fonts/[name].[hash:8].[ext]'
            }
          }
        }
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new ProgressPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: resolve('/public/index.html'),
      chunksSortMode: 'none'
    }),
    new CopyWebpackPlugin([
      {
        from: './public',
        to: './public',
        toType: 'dir',
        ignore: ['.DS_Store', '*.html']
      }
    ]),
    // 异步检查ts代码
    new ForkTsCheckerWebpackPlugin({
      vue: true,
      tslint: true,
      formatter: 'codeframe',
      checkSyntacticErrors: true
    })
  ]
}

完整开发依赖:

  yarn add -D webpack webpack-merge  vue-loader @babel/core babel-loader typescript ts-loader vue-style-loader css-loader postcss-loader autoprefixer sass node-sass sass-loader url-loader file-loader progress-bar-webpack-plugin copy-webpack-plugin html-webpack-plugin fork-ts-checker-webpack-plugin

完整项目目录:
基于webpack4.x手动实现vue+ts打包配置 -- 基础篇_第2张图片

最后,如果要在dev下查看效果,只需要:

  1. build下创建webpack.dev.js
  2. 写入如下配置:
const { HotModuleReplacementPlugin, DefinePlugin } = require('webpack')
const baseConfig = require('./webpack.base')
const webpackMerge = require('webpack-merge')

module.exports = webpackMerge(baseConfig, {
  devtool: 'cheap-module-eval-source-map', // 使用sourceMap
  devServer: {
      contentBase: false, // 由于使用了CopyWebpackPlugin.
      historyApiFallback: true, // 路由为history模式时需开始
      hot: true, //  开启代码热更新
      inline: true,
      host: 'localhost', // 如果需要局域网内访问,可设置为0.0.0.0
      port: 8080,
      open: true, // 编译后自动打开浏览器
    },
  plugins: [
    new DefinePlugin({
      'process.env': {
        NODE_ENV: '"development"' // 定义为开发模式
      },
      BASE_URL: '"./"' // 定义站点根路径,当部署路径不为/时,可在此设置,如/app1/
    }),
    new HotModuleReplacementPlugin(), // 代码热更新插件
      compilationSuccessInfo: {
        messages: [`项目运行地址: http://${devConfig.host}:${devConfig.port}`]
      }
    })
  ]
})
  1. package.jsonscripts中加上:
  "dev": "cross-env process.env.NODE_ENV=development webpack-dev-server --config build/webpack.dev.js"
  1. 这里我们可能需要安装一下依赖:
  yarn add -D cross-env webpack-dev-server
  1. 然后运行npm run dev或者yarn dev就可以看到效果了。

后续将继续分享更高级的配置,eslint+babel+ts+prettier代码自动格式化,CDN引入依赖,splitChunks代码分割,css抽取,scss全局变量等等…

你可能感兴趣的:(前端)