天天看点

手动配置webpack4.x配置打包vue项目脚手架

因为webpack4.x配置文件已经默认放到了node_modules里面,配置只能在根目录新建vue.config.js来进行配置,所以便动手基于wepack4.x手动配置了一个vue的脚手架,功能跟vue-cli类似,最主要的区别是将打包文件如:js、html、css放到了根目录,基本上是有了一个脚手架的雏形,更多细节还在优化中,接下来看看具体的配置吧。

首先在根目录创建了一个build文件,里面有三个文件:webpack.base.conf.js  webpack.dev.conf.js  webpack.prod.conf.js

手动配置webpack4.x配置打包vue项目脚手架

接下来看一下三个文件具体的实现逻辑:

1、webpack.base.conf.js

这个文件主要是包含了开发环境和生产环境共性的逻辑,基于打包环境做了一些判断:

const webpack = require('webpack');
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');

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

const MiniCssExtractPluginLoader = {
  loader:MiniCssExtractPlugin.loader,
  options:{
    publicPath: '../'
  }
}
/**
 *  css和scss开发、生产依赖
 *  生产分离css
 */
const cssConfig = [isProd ? MiniCssExtractPluginLoader : 'vue-style-loader', 
    {
      loader: 'css-loader',
      options: {
        sourceMap: !isProd,
      }
    }, 
    'postcss-loader'
  ],
  scssConfig = [isProd ? MiniCssExtractPluginLoader : 'vue-style-loader',
    'css-loader',
    {
      loader: 'postcss-loader',
      options: {
        sourceMap: !isProd
      }
    },
    'sass-loader'
  ];


module.exports = {
  mode: process.env.NODE_ENV,
  entry: {
    app: './src/main.js',
    vue: ['vue', 'vue-router', 'vuex'],
    vendor: ['axios']
  },
  output: {
    path: path.resolve(__dirname, '../'),
    publicPath: '',
    filename: 'js/[name].[hash:8].js',
    chunkFilename: 'js/chunk-[chunkhash:8].[id].js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: file => (
          /node_modules/.test(file) &&
          !/\.vue\.js/.test(file)
        )
      },
      {
        test: /\.css$/,
        use: cssConfig
      },
      {
        test: /\.scss$/,
        use: scssConfig
      },
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        test: /\.(png|jpe?g|gif|bmp|svg)$/,
        use: [{
          loader: 'url-loader',
          options: { // 配置图片编译路径
            limit: 8192, // 小于8k将图片转换成base64
            name: '[name].[hash:8].[ext]',
            outputPath: 'images/'
          }
        }, {
          loader: 'image-webpack-loader', // 图片压缩
          options: {
            bypassOnDebug: true
          }
        }]
      },
      {
        test: /\.html$/,
        use: [{
          loader: 'html-loader',
          options: { // 配置html中图片编译
            minimize: true
          }
        }]
      },
      {
        test: /\.(mp4|ogg|svg)$/,
        use: ['file-loader']
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 8192,
          name: 'fonts/[hash:8].[name].[ext]'
        }
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(), // vue加载器
    new HtmlWebpackPlugin({ // 处理模板
      title: '',
      filename: 'index.html',
      template: 'start.ejs',
      inject: true,
      minify: { // 对index.html压缩
        collapseWhitespace: isProd, // 去掉index.html的空格
        removeAttributeQuotes: isProd // 去掉引号
      }
      // hash: true // 去掉上次浏览器的缓存(使浏览器每次获取到的是最新的html)
    }),
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
      }
    }),
    new MiniCssExtractPlugin({ // 分离css
      filename: 'css/[name].[hash:8].css',
      chunkFilename: 'css/chunk-[hash:8].[id].css',
      allChunks: true
    }),
    new webpack.ProvidePlugin({ // 配置第三方库
      $http: 'axios' // 在.vue文件中可以使用$http发送请求,不用每次都import Axios from 'axios';也不用挂载到vue原型链上
    }),
    new FriendlyErrorsWebpackPlugin() // 识别某些类别的webpack错误,并清理,聚合和优先级
  ],
  resolve: {
    extensions: ['.js', '.vue', '.json', '.scss', '.less'], // import引入文件的时候不用加后缀
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': path.resolve(__dirname, '../src'),
      'component': path.resolve(__dirname, '../src/component'),
      'container': path.resolve(__dirname, '../src/container'),
    }
  },
  node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }

}
           

2、webpack.dev.conf.js

这个文件主要配置的是开发环境的依赖包括devServer,方便调试以及排错

const webpack = require('webpack');
const path = require('path');
const WebPackBaseConfig = require('./webpack.base.conf.js');
const { merge } = require('webpack-merge');

module.exports = merge(WebPackBaseConfig, {
    devtool: 'inline-source-map',
    devServer: {
      historyApiFallback: true,
      disableHostCheck: true,
      // contentBase: path.join(__dirname, ''), // 将 当前目录下的文件,作为可访问文件。
      compress: true, // 开启Gzip压缩
      host: '0.0.0.0', // 设置服务器的ip地址,默认localhost
      port: 3008, // 端口号
      hot: true,
      quiet: true,
      inline: false,
      open: true, // 自动打开浏览器
      openPage: 'xxx' // 自动打开浏览器的url
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin(), //启用热替换模块
      new webpack.NamedModulesPlugin() //启用HMR时,插件将显示模块的相对路径
    ]
})
           

3、webpack.prod.conf.js

这个文件是生产环境的配置,主要对编译做了一些优化,如:css编译,tree-shaking,js压缩混淆

const webpack = require('webpack');
const path = require('path');
const { merge } = require('webpack-merge');
const WebPackBaseConfig = require('./webpack.base.conf.js');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const glob = require('glob-all');
// const PurifyCSSPlugin = require('purifycss-webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = merge(WebPackBaseConfig, {
  stats: {
    assets: true, // 显示资产信息
    assetsSort: 'name', // 通过name排序 
    chunksSort: 'name', // chunk 通过name排序
    entrypoints: false, // 禁止告诉stats是否显示带有相应捆绑商品的入口点
    modules: false // 禁止打印 指示stats是否添加有关已构建模块的信息。
  },
  optimization: {
    splitChunks: {
      cacheGroups: {// 这里开始设置缓存的 chunks
        vendor: { // key 为entry中定义的 入口名称
          chunks: 'initial', // 必须三选一: "initial" | "all" | "async"(默认就是异步)
          test: /node_modules/, // 正则规则验证,如果符合就提取 chunk (指定是node_modules下的第三方包)
          name: 'vendor', // 要缓存的 分隔出来的 chunk 名称
          minChunks: 1,
          enforce: true
        },
        styles: {
          chunks: 'all',
          test: /\.(css|scss)$/,
          name: 'vendor',
          minChunks: 1,
          enforce: true
        }
      }
    },
    minimize: true, // 告诉webpack使用TerserPlugin或中指定的插件最小化捆绑optimization.minimizer
    minimizer: [ // js css打包压缩
      (compiler) => {
        const TerserPlugin = require('terser-webpack-plugin'); // js压缩优化 用terser-webpack-plugin替换掉uglifyjs-webpack-plugin解决uglifyjs不支持es6语法问题
        new TerserPlugin({
          parallel: true, // 使用多进程并行运行可提高构建速度。并发运行的默认次数:os.cpus().length - 1
          sourceMap: true, 
          extractComments: false, //禁止提取注释到LICENSE.txt文件中
          terserOptions: {
            compress: {
              pure_funcs: ["console.log"] // 去除console.log打印信息
            }
          }
        }).apply(compiler);
      },
      new OptimizeCSSAssetsPlugin({})
    ]
  },
  plugins: [
    new CleanWebpackPlugin({ //清理文件夹
      cleanOnceBeforeBuildPatterns: ['js', 'css', 'images', '*.html']
    }),
    // new PurifyCSSPlugin({
    //   paths: glob.sync([ // css tree shaking 移除项目中我们没有用到css代码,减小css的打包体积
    //     path.join(path.resolve(__dirname,"../src/*."))
    //   ])
    // })
  ]
})
           

4、根目录创建.babelrc ,编译es6

{
  "presets": ["@babel/preset-env"],
  "plugins": [
    "@babel/plugin-transform-runtime",
    "@babel/plugin-proposal-class-properties",
    ["import", {
      "libraryName": "vant",
      "libraryDirectory": "es",
      "style": true
    }]
  ]
}
           

5、根目录创建postcss.config.js,解析scss

module.exports = {
    loader: 'postcss-loader',
    plugins: [
        require('autoprefixer')
    ]
}
           

6、根目录创建start.ejs,以ejs为模板 

<!DOCTYPE html>
<html >
  <head>
    <meta charset="utf-8">
    <meta content="telephone=no" name="format-detection" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
    <meta http-equiv="Expires" CONTENT="0"> 
    <meta http-equiv="Cache-Control" CONTENT="no-cache"> 
    <meta http-equiv="Pragma" CONTENT="no-cache">
    <title><%= htmlWebpackPlugin.options.title %></title>
    <script type="text/javascript">
      Date.prototype.Format = function (fmt) { 
        fmt = fmt || 'yyyy-MM-dd hh:mm:ss';
          var o = {
              "M+": this.getMonth() + 1, //月份 
              "d+": this.getDate(), //日 
              "h+": this.getHours(), //小时 
              "m+": this.getMinutes(), //分 
              "s+": this.getSeconds(), //秒 
              "q+": Math.floor((this.getMonth() + 3) / 3), //季度 
              "S": this.getMilliseconds() //毫秒 
          };
          if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
          for (var k in o)
          if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
          return fmt;
      }
      function adapt(designWidth, rem2px) {
        var d = window.document.createElement('div');
        d.style.width = '1rem';
        d.style.display = "none";
        var head = window.document.getElementsByTagName('head')[0];
        head.appendChild(d);
        var defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));
        document.documentElement.style.fontSize = window.innerWidth / designWidth * rem2px / defaultFontSize * 100 + '%';
        var st = document.createElement('style');
        var portrait = "@media screen and (min-width: " + window.innerWidth + "px) {html{font-size:" + ((window.innerWidth / (designWidth / rem2px) / defaultFontSize) * 100) + "%;}}";
        var landscape = "@media screen and (min-width: " + window.innerHeight + "px) {html{font-size:" + ((window.innerHeight / (designWidth / rem2px) / defaultFontSize) * 100) + "%;}}"
        st.innerHTML = portrait + landscape;
        head.appendChild(st);
        return defaultFontSize
      };
      var defaultFontSize = adapt(750, 100);
    </script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
           

7、package.json配置

{
  "name": "xxx",
  "description": "xx",
  "version": "1.0.0",
  "author": "[email protected]",
  "private": true,
  "scripts": {
    "start": "cross-env NODE_ENV=development webpack-dev-server --config ./build/webpack.dev.conf.js",
    "build": "cross-env NODE_ENV=production webpack --config ./build/webpack.prod.conf.js"
  },
  "dependencies": {
    "axios": "^0.19.2",
    "babel-preset-es2015": "^6.24.1",
    "better-scroll": "^1.15.2",
    "es6-promise": "^4.2.8",
    "vue": "^2.6.11",
    "vue-router": "^3.3.4",
    "vuex": "^3.5.1"
  },
  "devDependencies": {
    "@babel/core": "^7.10.4",
    "@babel/plugin-transform-runtime": "^7.10.4",
    "@babel/polyfill": "^7.10.4",
    "@babel/preset-env": "^7.10.4",
    "@babel/runtime": "^7.10.4",
    "autoprefixer": "^9.8.4",
    "babel-loader": "^8.1.0",
    "babel-plugin-import": "^1.13.0",
    "babel-plugin-syntax-dynamic-import": "^6.18.0",
    "babel-plugin-transform-remove-console": "^6.9.4",
    "clean-webpack-plugin": "^3.0.0",
    "cross-env": "^7.0.2",
    "css-loader": "^3.6.0",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^6.0.0",
    "friendly-errors-webpack-plugin": "^1.7.0",
    "glob-all": "^3.2.1",
    "html-webpack-plugin": "^4.3.0",
    "image-webpack-loader": "^6.0.0",
    "less": "^3.12.2",
    "less-loader": "^6.2.0",
    "mini-css-extract-plugin": "^0.9.0",
    "node-sass": "^4.14.1",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "purify-css": "^1.2.5",
    "purifycss-webpack": "^0.7.0",
    "rimraf": "^3.0.2",
    "sass-loader": "^9.0.1",
    "terser-webpack-plugin": "^3.0.6",
    "url-loader": "^4.1.0",
    "vue-loader": "^15.9.3",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.11",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0",
    "webpack-merge": "^5.0.7"
  }
}