手把手教你webpack4.x从零开始搭建vue项目

3,161 阅读8分钟

1.首先安装各个模块所需要的依赖

  • npm init -y  初始化package.json文件 。

     以下所有安装命令的 ‘-D‘  代表安装在该文件的devDependencies中
npm的文档说明:dependencies是运行时依赖,devDependencies是开发时的依赖

  • npm i webpack webpack-cli webpack-dev-server webpack-merge -D

      webpack可以看做是模块打包机。webpack-cli封装了与CLI处理相关的所有代码。它捕获选项并将它们发送到webpack编译器。webpack-dev-server是webpack官方提供的一个小型express服务器, 为webpack打包生成的资源文件提供web服务。webpack-merge是和用来区分两个不同的环境

  • npm i cross-env -D

      可以跨平台设置环境变量

  • npm i @babel/core babel-loader @babel/preset-env -D

      @babel/core是转译器本身,提供了babel的转译API。babel-loader就是调用这些API来完成转译js过程的。Babel 7宣布废弃babel-preset-es201x而采用新的env插件

  • **npm i  @babel/polyfill ****@babel/runtime-corejs2 **@babel/plugin-transform-runtime -D

      Babel默认只转换新的JavaScript句法,而不转换新的API。需要在入口加载@babel/polyfill插件来转换,可以解决某些方法在IE的兼容性问题(这种方式是通过向全局对象和内置对象的prototype上添加方法实现的,会造成全局变量污染),在babel7.x版本中使用 :@babel/runtime-corejs2,可以结合@babel/plugin-transform-runtime, 可避免全局污染。

      在转换 ES2015 语法为 ECMAScript 5 的语法时,babel 会需要一些辅助函数,例如 _extend。babel 默认会将这些辅助函数内联到每一个 js 文件里,这样文件多的时候,项目就会很大。所以 babel 提供了 transform-runtime 来将这些辅助函数“搬”到一个单独的模块 babel-runtime 中,这样做能减小项目文件的大小。

  • 版本babel7.x弃用了@babel/preset-stage-0的用法,建议需要什么插件,单独去安装

  • npm i @babel/plugin-proposal-decorators -D

      将类和对象装饰器编译为ES5

  • npm i @babel/plugin-syntax-dynamic-import -D (在babel7.x中似乎并不需要再进行单独安装)

     允许解析import() 延迟加载,懒加载

  • npm i @babel/plugin-proposal-optional-chaining -D

     可以访问深层嵌套的属性,可以不用逻辑与去判断    // const baz = obj?.foo?.bar?.baz

注意: babel7.0后,已经不再使用@babel/preset-stage-x的预设了, 所需的插件都需要单独去安装配置,根据公司的项目需要,可以选择性的去配置 :@babel/plugin-***    这里不做过多赘述。

  • npx eslint --init

直接执行此命令,可以一键安装eslint语法规范的依赖包

  • npm i babel-eslint -D

需要在。eslintrc.js文件的parserOptions对象下,添加parser: 'babel-eslint'属性,表示:引入转换插件(解决import等一些eslint校验问题)

  • npm i vue-loader vue-template-compiler -D

       解析vue文件需要安装这两个插件

  • npm i uglifyjs-webpack-plugin -D

       用来在生产环境压缩js

  • npm i compression-webpack-plugin -D

       开启gzip压缩

  • npm i css-loader less less-loader sass-resources-loader postcss-loader autoprefixer mini-css-extract-plugin optimize-css-assets-webpack-plugin -D

       css-loader处理css,用来解析@import这种语法。less-loader将less语言转换成css,sass-resources-loader是注册全局less或者sass文件变量,方便单个页面引用。 postcss-loader 和 autoprefixer用来为css的样式自动添加浏览器前缀,mini-css-extract-plugin是提取css文件,不再以style标签存放,而是创建link标签引入,所以不再需要style-loader。optimize-css-assets-webpack-plugin用来压缩css

  • npm i url-loader file-loader -D

       url-loader打包图片资源,file-loader打包字体等资源

  • npm i html-webpack-plugin clean-webpack-plugin copy-webpack-plugin -D

       html-webpack-plugin用来打包时自动生成html文件,clean-webpack-plugin打包时会自动清除dist,copy-webpack-plugin用来拷贝静态资源到打包目录

  • npx eslint --init, npm i babel-eslint -D

npx eslint --init 一键配置eslint规范。此外需要安装babel-eslint工具。创建.eslintignore文件可设置忽略检测的文件。在webstorm工具中设置eslint,编译器可以实时检测,显示错误

安装完上述依赖之后,需要根据自己的安装去配置.babelrc(babel的配置文件)和postcss.config.js。这里直接附上我自己的配置仅供参考。如下:

  • .babelrc

    { "presets": [ "@babel/preset-env" ], "plugins": [ ["@babel/plugin-transform-runtime", { "corejs": 2 }], [ "@babel/plugin-proposal-decorators", { "legacy": true } ], "@babel/plugin-proposal-optional-chaining" ] }

  • postcss.config.js

    module.exports = {
    plugins: [ require('autoprefixer')
    ] }

2. webpack公共文件配置 :webpack.base.config.js

let path = require('path')
let MiniCssExtractPlugin = require('mini-css-extract-plugin')   // 提取css公共文件
let { VueLoaderPlugin } = require('vue-loader')     //vue-loader  15.x版本以后需要引入
let HtmlWebpackPlugin = require('html-webpack-plugin')    //自动生成html
let CopyWebpackPlugin = require('copy-webpack-plugin')  //拷贝静态资源
let { CleanWebpackPlugin } = require('clean-webpack-plugin')  //打包前清除dist
let CompressionWebpackPlugin = require('compression-webpack-plugin')
let webpack = require('webpack')

module.exports = {
  entry: [
    '@babel/polyfill', 
    path.resolve(__dirname, '../src/main.js') 
  ],
  output: {
    filename: 'js/[name].[hash:8].js',   //打包后的js文件放在js目录下,添加hash值防止缓存
    chunkFilename: 'js/[name].[hash:8].js', // 配合按需加载路由来使用,用来修改打包后的各个JS模块文件名字,具体请看下方打包后的截图
    path: path.resolve(__dirname, '../dist'), //输出的目录
    // publicPath: '/'     //静态资源cdn的地址。publicPath的使用是分环境的。在使用html-webpack-plugin 生成index.html时,publicPath是可以不用配置的。
  },
  stats: {     // 本地起服务或者打包时候,清除过多的日志信息,精简控制台信息。
    modules: false,
    children: false,
    chunks: false,
    chunkModules: false,
    assets: false,  //不显示资源打包信息
  },
  module: {   //转换规则
    rules: [
      {
        test: /\.vue$/,
        exclude: /node_modules/,
        use: ['vue-loader']
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader']
      },
      {
        test: /\.(css|less)$/,   
        use: [
          {
            loader: MiniCssExtractPlugin.loader,            options: {  // webpack在打包时,首先会把图片打包到 /dist/images/ 文件夹下,但是在css文件中引用时,会将路径替换为下面的publicPath + name。
              publicPath: '../'  
            }
          },
          'css-loader',
          'postcss-loader',
          'less-loader',
           {
             loader: 'sass-resources-loader',
             options: {
               path.resolve(__dirname, '此处写自己配置的公共变量文件路径,如:'../src/assets/styles/common.less' ')
             }
           }
        ],
      },
      {
        test: /\.(png|jpe?g|svg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 20 * 1024   // 不设置这个的话,打包后的图片默认是超过8k时,会以base64编码
              name: 'images/[name].[hash:8].[ext]',
              esModule: false   // 解决img标签引入的图片打包后显示不出来图片的问题,F12控制台显示为:src="[Object Module]"
            }
          }
        ]
      },
      {
        test:/\.(woff|woff2|eot|ttf|otf)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: 'fonts/[name].[hash:8].[ext]'
            }
          }
        ]
      },
    ]
  },
  resolve: {
    extensions: ['.vue', '.js', '.scss',  '.json'],// 能够使用户在引入模块时不带扩展名字, 自动解析
    alias: {    //别名,方便快速查找模块
      '@': path.resolve(__dirname, '../src')
    }
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: path.resolve(__dirname, '../index.html'),
      title: '这是自定义的名字,请随意',
      favicon: path.resolve(__dirname, '../src/images/logon.png'),  //浏览器标题的图标
      hash: true,
      minify: {
        removeAttributeQuotes: true,    //删除属性的引号
        collapseWhitespace: true    //删除空白符与折叠行
      }
    }),
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, '../src/assets'),
          to: path.resolve(__dirname, '../dist/copyAssets')
        }
      ]
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash:8].css'
    }),
    new webpack.ProgressPlugin()      //显示打包进度的插件
    new CompressionWebpackPlugin()    // 开启gzip压缩
  ]
}

3.webpack开发环境配置 :webpack.dev.config.js

let webpack = require('webpack') 
let path = require('path') 
let merge = require('webpack-merge')     
let base = require('./webpack.base.config')

module.exports = merge(base, {
  mode: 'development',     // 定义环境变量
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    port: 8000,     //设置端口
    progress: true,// 控制台显示百分比,HDM进度
    hot: true,     // 启用热更新,控制台全部刷新
    open: true,    // 自动打开浏览器
    compress: true, // 配置是否启用gzip压缩
    historyApiFallback: true,
    contentBase: path.resolve(__dirname, '../dist'),
    proxy: {
      '/api': {        //url中匹配到'/api', 就会把'/api'之前的东西全部替换成target
        target: '',      // 目标服务器host
        changeOrigin: true,   //  表示要改变原始host
        secure: false,   // 默认请求的服务是https的, 并且证书是未认证的,所以需要关闭安全检测。
        clentLogLevel: 'none',  //当使用内联模式(inline mode)时,会在开发工具(DevTools)的控制台(console)显示消息,例如:在重新加载之前,在一个错误之前,或者 模块热替换(Hot Module Replacement) 启用时。默认值是 info。
        pathRewrite: {
          '^/api': ''   // 重写请求,源访问地址中包含'/api'的将会替换为空
        }
      }
    }
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),   //热更新插件
    new webpack.NamedChunksPlugin()    // 使用此插件热更新时控制台会显示模块的相对路径
  ]
})

4.webpack生产环境配置 :webpack.prod.config.js

let merge = require('webpack-merge')
let base = require('./webpack.base.config')
let UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
let OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = merge(base, {
  mode: 'production',
  devtool: 'cheap-module-source-map',
  optimization: {
    minimizer: [
      new UglifyjsWebpackPlugin({   // 生产环境压缩JS
        cache: true,    //是否否启用缓存
        parallel: true,   //多通道并行处理
        sourceMap: false, //生产环境关闭源码映射
        uglifyOptions: {
          warnings: false,    //清除警告
          compress:{
            drop_debugger: true,	// 清除degugger
            drop_console: true   //清除所有的console信息
          }
        }
      }),
      new OptimizeCssAssetsPlugin()   // 生产环境压缩css
    ],
    splitChunks: {   //用于拆分代码,找到 chunk 中共同依赖的模块进行“提取”和“分离”到单独的文件中,减少打包后体积,可以避免内存溢出的问题。
      chunks: 'all'
    }
  },
   performance: {                       //  webpack 的性能提示
     hints: 'warning',                 // 显示警告信息
     maxEntrypointSize: 5 * 1024 * 1024,    // 设置入口文件的最大体积为5M  (以字节为单位)
     maxAssetSize: 20* 1024 * 1024,        // 设置输出文件的最大体积为20M  (以字节为单位)
     assetFilter (assetFilename) {        // 提供资源文件名的断言函数
      return assetFilename.endsWith('.js') || assetFilename.endsWith('.css')
     }
    }
})

4.最后一步

在package.json文件的scripts中添加如下,然后执行npm run build即可打包

"serve": "cross-env NODE_ENV=development webpack-dev-server --config ./build/webpack.dev.config.js",
"build": "cross-env NODE_ENV=production webpack --config ./build/webpack.prod.config.js"

5.总结

       以上配置是采用webpack4.x+babel7.x版本,自行配置时请注意版本兼容问题。如果您想要升级项目到babel7.0, 文档中也提供了一个升级工具:执行 npx babel-upgrade--write--install一键安装babel7.0所需要的所有配置,并且会自动将配置写入package.json和.babelrc文件中。详情请参考文档说明。以下是打包后的截图:

copyAssets是拷贝的静态资源。为什么打包后的js会有0**,2** 这么几个文件呢?这是因为我vue的路由使用了按需加载,每个页面都生成自己单独的js。等需要的时候,才会去加载这些js的。但是这个0, 1, 2,...等等的文件名字默认是以id命名的,这看起来不友好,而且不太方便排错,所以需要在引入路由时候,需要使用特殊的注释语法来提供webpackChunkName,配合output里面设置的chunkFilename: 'js/[name].[hash:8].js'来修改文件名,代码如下:

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '',
      redirect: '/Home'
    },
    {
      path: '/Home',
      name: 'Home',
      component: () => import(/* webpackChunkName: 'Home' */ '@/view/Home')
    },
    {
      path: '/Hello',
      name: 'Hello',
      component: () => import(/* webpackChunkName: 'Hello' */ '@/view/Hello')
    }
  ]
})

最终打包后的结果如下,看看,这就舒服多了~