【项目】webpack+vue---手动搭建项目(上篇:webpack搭建配置)

361 阅读9分钟

前言:

现在的前端项目,经常需要配置webpack,在很多前端招聘要求里,webpack打包、webpack工程化等字眼也越来越高,我们经常使用vue-cli脚手架来搭建我们的项目,它已经自动配置好了打包内容,那么,webpack的原理究竟是怎么样的,我们如果能手动搭建出来,webpack的配置多了,慢慢的就会得心应手,不要怕它,要去拥抱它,因为webpack在前端行业中已经成为不可或缺的一部分,让我们看看,怎样手动搭建一个webpack项目。 image.png

项目架构

> build // webpack配置文件夹
  utils.js // 公用方法
  webpack.base.conf.js // 公用配置
  webpack.dev.conf.js // 开发环境配置
  webpack.prod.conf.js // 生产环境配置
> dist // webpack打包生成文件夹
  > static
    > css
    > fonts
    > images
    > js
  index.html
> src
  > assets // 静态资源
    > css
    > fonts
    > images
    > js
  App.vue // vue入口文件
  main.js // webpack打包入口文件
bable.config.js // babel配置文件(es6转码)
package.json // 项目配置文件
index.html // html模板

运行环境和依赖版本

版本问题是项目运行受阻最主要的一个原因,根据以下版本进行安装,出错明显少了很多

  • node版本 10.15.3
  • webpack版本 4.31.0
  • webpack-cli版本 3.3.2
  • webpack-dev-server版本 3.3.1
"dependencies": {
    "vue": "^2.6.10",
    "vue-loader": "^15.7.0",
    "vue-template-compiler": "^2.6.10"
  },
  "devDependencies": {
    "@babel/core": "^7.4.4",
    "@babel/polyfill": "^7.4.4",
    "@babel/preset-env": "^7.4.4",
    "autoprefixer": "^9.5.1",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^2.0.2",
    "core-js": "^3.0.1",
    "css-loader": "^2.1.1",
    "file-loader": "^3.0.1",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.9.0",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.6.0",
    "postcss-loader": "^3.0.0",
    "style-loader": "^0.23.1",
    "url-loader": "^1.1.2",
    "webpack": "^4.31.0",
    "webpack-cli": "^3.3.2",
    "webpack-dev-server": "^3.3.1",
    "webpack-merge": "^4.2.1"
  }

1 webpack 快速生成文件

我们首先配置一个webpack,看看实现生成文件的过程是怎样的

1.1 初始化项目,快速生成package.json配置文件

npm init -y

1.2 新建build文件夹,新建webpack.base.conf.js文件

// ./build/webpack.base.conf.js

const path = require('path')

module.exports = {
  // 入口主文件
  entry: path.resolve(__dirname, '../src/main.js'),
  // 出口配置
  output: {
    path: path.resolve(__dirname, '../dist'), // 出口路径
    filename: '[name].js' // 出口主文件路径和文件名
  }
}

1.3 新建src文件夹,新建mian.js文件

// ./src/main.js

document.write("Hello, webpack!")

1.4 配置 scripts 执行命令

  • webpack 打包工具
  • webpack-cli 改善定制webpack配置的设置
cnpm i -D webpack webpack-cli
// ./package.json

"scripts": {
  "test": "webpack --config ./build/webpack.base.conf.js"
}

1.5 运行 webpack

npm run test

image.png image.png

可以看到多了一个 dist文件夹,里面有一个生成的main.js文件,通过查找可以看出,文件已经成功生成

2 webpack 环境分析

webpack目前可以生成文件了,但是我们一般做项目的时候都需要区分开发环境和生产环境,因为开发环境注重的是调试方便,而生产环境注重的是性能,运行速度等,所以我们一般把开发环境和生产环境共用的配置,写在一个公用配置文件,然后分别在不同的环境中引入公用环境配置

下面,我们开始配置webpack环境:

// ./build
utils.js
webpack.base.conf.js // 公用环境
webpack.dev.conf.js // 开发环境
webpack.prod.conf.js // 生产环境

utils.jswebpack打包公用文件,先封装好路径,方便调用

// ./build/utils.js

const path = require('path')

module.exports = {
  // 绝对路径(相对build文件夹)
  resolvePath: (dir = './') => {
    return path.resolve(__dirname, dir)
  },
  // static路径
  staticPath: (dir = './') => {
    return path.posix.join('static', dir)
  }
}

3 公用环境配置

公用环境配置文件是webpack.base.conf.js,该环境配置用于开发环境生产环境,一般情况下这两个环境能够公用的配置都集中在公用环境实现

3.1 主要处理内容:

  • 文件入口、出口
  • html自动生成(免手动修改路径或文件名)
  • 文件免后缀配置
  • 路径快捷方式配置
  • js文件ES6转ES5
  • vue文件处理
  • 图片文件处理
  • 文字文件处理

3.2 文件入口、出口

// ./build/webpack.base.conf.js

const utils = require('./utils')

module.exports = {
  // 入口主文件
  entry: utils.resolvePath('../src/main.js'),
  // 出口配置
  output: {
    path: utils.resolvePath('../dist'), // 出口路径
    filename: utils.staticPath('js/') + '[name].[hash:4].js' // 出口主文件路径和文件名
  }
}

[hash:n]是为了解决文件缓存问题,会生成带n位哈希值字符的文件

3.3 html自动生成(免手动修改路径或文件名)

  • html-webpack-plugin 自动生成同步哈希值的html文件,避免手动改动引入的文件路径和文件名
cnpm i -D html-webpack-plugin
// ./build/webpack.base.conf.js

const HtmlWebpackPlugin = require('html-webpack-plugin')
...
plugins: [
  new HtmlWebpackPlugin({
    template: utils.resolvePath('../index.html'), // html模板
    filename: 'index.html', // 生成html文件名
    //inject: 'body' // 生成入口文件引入代码位置:head、body,默认body
  })
]
...
  • 新增index.html文件
/* ./index.html */

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>webpack-vue</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
  • 运行webpack
npm run test

image.png 可以看到,生成index.html文件引入的main.js已自动修改

3.4 文件免后缀配置

类似import、require引入的文件,配置可以省略以下后缀

// ./build/webpack.base.conf.js

...
  resolve: {
    extensions: ['.js', '.json', '.vue'] // 文件引入免后缀
  }
...

3.5 路径快捷方式配置

// ./build/webpack.base.conf.js

...
  resolve: {
    alias: {
      '@': utils.resolvePath('../src') // src快捷路径
    }
  }
...

例如:

import '@/assets/images/01.jpg'

import '@/assets/css/base.css'

3.6 js文件ES6转ES5

虽然大部分浏览器已经支持ES6语法,但是还是有小部分不支持的,为确保我们写的js能正常使用,需要把ES6语法转义为ES5语法,并实现兼容

  • babel-loader 只支持ES6语法转换,ES6新增的语法(如promise、Set、Maps、Proxy等则不支持)
  • @babel/core 把js代码分析成ast,方便各个插件分析语法进行相应的处理
  • @babel/polyfillES6新增的语法转换(编译出来的文件会大很多,如果项目没用到新增语法,则不引入)
  • @babel/preset-env可以配置让js兼容的运行环境
  • core-jsES6新增的语法api
cnpm i -D babel-loader @babel/core @babel/polyfill @babel/preset-env core-js

3.6.1 配置文件内容如下:

// ./build/webpack.base.conf.js

...
module: {
  rules: [
    {
      test: /\.jsx?$/,
      loader: 'babel-loader',
      exclude: /node_module/ // 排除该路径文件,避免花费大量时间在这里
    }
  ]
}
...

3.6.2 根目录创建babel.config.js文件,内容如下:

// ./babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 3
      }
    ]
  ]
}

3.6.3 main.js补充内容如下:

// ./src/main.js

// import '@babel/polyfill' // 貌似已弃用,可用以下俩代替
import 'core-js/stable'
import 'regenerator-runtime/runtime'

3.7 vue文件处理

cnpm i -D vue vue-loader vue-template-compiler
// ./build/webpack.base.conf.js

...
const VueLoaderPlugin = require('vue-loader/lib/plugin')
...
module: {
  rules: [
    {
      test: /\.vue$/,
      loader: 'vue-loader'
    }
  ]
},
plugins: [
  new VueLoaderPlugin()
]
...

3.8 图片文件处理

url-loaderfile-loader的区别是,url-loader可设置limit

url-loader封装了file-loader,url-loader不依赖于file-loader,因为url-loader内置了file-loader

当文件的大小小于或等于limit设定数值时,url-loader将会把文件转为DataURL,渲染为base64字符串

当文件大小大于limit设定数值时,url-loader会调用file-loader进行处理

cnpm i -D url-loader
// ./build/webpack.base.conf.js

...
module: {
  rules: [
    {
      test: /\.(jpe?g|png|gif)$/,
      use: [
        {
          loader: 'url-loader',
          options: {
            limit: 1024 * 10, // 单位:b
            name: utils.staticPath('images/') + '[name]-[hash:4].[ext]'
          }
        }
      ]
    }
  ]
}
...

3.9 文字文件处理

cnpm i -D url-loader
// ./build/webpack.base.conf.js

module: {
  rules: [
    {
      test: /\.(woff2?|eot|ttf|otf|svg)$/,
      use: [
        {
          loader: 'url-loader',
          options: {
            limit: 1,
            name: utils.staticPath('fonts/') + '[name]-[hash:4].[ext]'
          }
        }
      ]
    }
  ]
}

3.10 完整代码展示:

// ./build/webpack.base.conf.js
const utils = require('./utils')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  entry: utils.resolvePath('../src/main.js'),
  output: {
    path: utils.resolvePath('../dist'),
    filename: utils.staticPath('js/') + '[name].[hash:4].js'
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: [
          {
            loader: 'babel-loader'
          }
        ],
        exclude: /node_module/
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.(jpe?g|png|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 1,
              name: utils.staticPath('images/') + '[name]-[hash:4].[ext]'
            }
          }
        ]
      },
      {
        test: /\.(woff2?|eot|ttf|otf|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 1,
              name: utils.staticPath('fonts/') + '[name]-[hash:4].[ext]'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: utils.reslovePath('../index.html'),
      filename: 'index.html'
    }),
    new VueLoaderPlugin()
  ],
  resolve: {
    extensions: ['.js', 'json', 'vue'],
    alias: {
      '@': utils.resolvePath('../src')
    }
  }
}

4 开发环境配置

开发环境配置文件是webpack.dev.conf.js,该环境配置侧重于调试效率

4.1 主要处理内容:

  • 合并公用webpack配置
  • 设置devtool方便开发调试
  • 处理less和css文件,为其自动添加样式前缀,并使其以style的形式展示在页面中
  • 配置devServer,修改webpack-dev-server的默认配置

4.2 合并公用webpack配置

  • webpack-merge用于合并webpack配置
cnpm i -D webpack-merge
// ./build/webpack.dev.conf.js

const webpackMerge = require('webpack-merge')
const webpackBase = require('./webpack.base.conf')

module.exports = webpackMerge(webpackBase, {
  mode: 'development'
})

4.3 启动开发环境webpack

  • webpack-dev-server 支持自动刷新的webpack,一般用于开发环境
cnpm i -D webpack-dev-server
// ./build/webpack.dev.conf.js

...
devServer: {
  port: 9000
}
...

启动命令配置

// ./package.json

"scripts": {
    "dev": "webpack-dev-server --inline --progress --config ./build/webpack.dev.conf.js"
  }

运行命令

npm run dev

浏览器查看效果

localhost:9000

启动完之后,访问:location:9000,查看效果

4.4 设置devtool方便开发调试

// ./build/webpack.dev.conf.js
...
devtool: 'inline-source-map'
...

4.5 处理less和css文件,为其自动添加样式前缀,并使其以style的形式展示在页面中

  • less-loader处理less
  • postcss-loader处理自动添加样式前缀
  • autoprefixer处理自动添加样式前缀
  • css-loader处理css
  • style-loader处理style
cnpm i -D less-loader postcss-loader autoprefixer css-loader style-loader
// ./build/webpack.dev.conf.js

...
module: {
  rules: [
    {
      test: /\.(c|le)ss$/,
      use: [
        { loader: 'style-loader' },
        { loader: 'css-loader' },
        {
          loader: 'postcss-loader',
          options: {
            ident: 'postcss',
            sourceMap: true,
            plugins: (loader) => [
              require('autoprefixer')({
                overrideBrowserslist: ['last 2 versions', '> 1%']
              })
            ]
          }
        },
        { loader: 'less-loader' }
      ]
    }
  ]
}
...

4.6 完整代码展示:

// ./build/webpack.dev.conf.js

const webpackMerge = require('webpack-merge')
const webpackBase = require('./webpack.base.conf')

module.exports = webpackMerge(webpackBase, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    prot: 9000
  },
  module: {
    rules: [
      {
        test: /\.(c|le)ss$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' },
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              sourceMap: true,
              plugins: (loader) => [
                require('autoprefixer')({
                  overrideBrowserslist: ['last 2 versions', '> 1%']
                })
              ]
            }
          },
          { loader: 'less-loader' }
        ]
      }
    ]
  }
})

5 生产环境配置

生产环境配置文件是webpack.prod.conf.js,该环境配置侧重于代码压缩和性能

5.1 主要处理内容:

  • 合并公用webpack配置
  • 设置devtool方便追踪代码
  • 每次打包前都先清空dist打包文件夹
  • 处理less和css文件,为其自动添加样式前缀,并使其抽离样式为单独css文件

5.2 合并公用webpack配置

// ./build/webpack.prod.conf.js

const webpackMerge = require('webpack-merge')
const webpackBase = require('./webpack.base.conf')

module.exports = webpackMerge(webpackBase, {
  mode: 'production'
})

5.3 启动生产环境webpack

启动命令配置

// ./package.json

"scripts": {
    "build": "webpack --config ./build/webpack.prod.conf.js"
  }

运行命令

npm run build

查看dist文件夹变化

5.4 设置devtool方便开发调试

// ./build/webpack.prod.conf.js

...
devtool: 'eval'
...

5.5 每次打包前都先清空dist打包文件夹

  • clean-webpack-plugin清空打包文件夹
cnpm i -D clean-webpack-plugin
// ./build/webpack.prod.conf.js

const CleanWebpackPlugin = require('clean-webpack-plugin')

...
plugins: [
  new CleanWebpackPlugin()
]
...

5.6 处理less和css文件,为其自动添加样式前缀,并使其以style的形式展示在页面中

  • less-loader处理less
  • postcss-loader处理自动添加样式前缀
  • autoprefixer处理自动添加样式前缀
  • css-loader处理css
  • mini-css-extract-plugin抽离样式为单独css文件 因为前面几项依赖在配置开发环境的时候已经安装,所以现在只安装mini-css-extract-plugin即可
cnpm i -D mini-css-extract-plugin
// ./build/webpack.dev.conf.js
const utils = require('./utils')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

...
module: {
  rules: [
    {
      test: /\.(c|le)ss$/,
      use: [
        {
          loader: MiniCssExtractPlugin.loader,
          options: { publicPath: '../../' } // 公用路径调试到外层,直到样式路径正确为止
        },
        { loader: 'css-loader' },
        {
          loader: 'postcss-loader',
          options: {
            ident: 'postcss',
            sourceMap: true,
            plugins: (loader) => [
              require('autoprefixer')({
                overrideBrowserslist: ['last 2 versions', '> 1%']
              })
            ]
          }
        },
        { loader: 'less-loader' }
      ]
    }
  ]
},
plugins: [
  new MiniCssExtractPlugin({
    filename: utils.staticPath('css/') + '[name].[hash:4].css'
  })
]
...

5.7 完整代码展示:

// ./build/webpack.prod.conf.js

const utils = require('./utils')
const webpackMerge = require('webpack-merge')
const webpackBase = require('./webpack.base.conf')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = webpackMerge(webpackBase, {
  mode: 'production',
  devtool: 'eval', // cheap-source-map
  module: {
    rules: [
      {
        test: /\.(c|le)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: { publicPath: '../../' } // 公用路径调试到外层
          },
          { loader: 'css-loader' },
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              sourceMap: true,
              plugins: (loader) => [
                require('autoprefixer')({
                  overrideBrowserslist: ['last 2 versions', '> 1%']
                })
              ]
            }
          },
          { loader: 'less-loader' }
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: utils.staticPath('css/') + '[name].[hash:4].css'
    })
  ]
})

6 src文件夹文件增改

  • 新增assets静态资源文件夹
  • 修改main.js入口文件
  • 修改App.vue入口文件

6.1 新增assets静态资源文件夹

6.1.1 新增css样式文件

/* ./src/assets/css/base.css */

html {
  display: flex;
}
/* ./src/assets/css/index.less */

html {
  height: 100%;
  background: #f45 url(../images/02.jpg) center no-repeat;
}

6.1.2 新增images图片文件

./src/assets/images/01.jpg
./src/assets/images/02.jpg

6.1.3 新增fonts文字文件

./src/fonts/(生成的文字图标文件)

image.png

6.2 修改main.js入口文件

完整代码展示:

// ./src/main.js

// import '@babel/polyfill'
// import 'core-js/stable'
// import 'regenerator-runtime/runtime'

import Vue from 'vue'
import App from './App'

import '@/assets/fonts/iconfont.css'
import '@/assets/css/base.css'
import '@/assets/css/index.less'

new Vue({
  el: '#app',
  render: (h) => h(App)
})

6.3 修改App.vue入口文件

完整代码展示:

// ./src/App.vue

<template>
  <div class="app">
    <h1>vue app</h1>
    <p><img :src="imgSrc" /></p>
    <div v-for="(item, index) in count" :key="index">
      <span class="iconfont icon-dizhiaddress"></span>
      <span>{{ item }}</span>
    </div>
  </div>
</template>

<script>
import imgSrc from '@/assets/images/01.jpg'

export default {
  data() {
    return {
      imgSrc,
      count: 20
    }
  }
}
</script>

<style lang="less" scoped>
.app {
  color: yellow;
}
</style>

7 启动命令查看效果

7.1 开发环境

npm run dev

浏览器查看效果

localhost:9000

启动完之后,访问:location:9000,查看效果,页面已经渲染成功,修改vue文件内容然后保存,我们看到页面也会自动更新对应的内容,开发环境配置成功

7.2 生产环境

npm run build

编译完之后,dist文件夹会生成对应的文件

image.png

浏览器打开index.html文件查看效果

总结:

还是不断重复敲多几次,敲到熟练即可,熟悉它的工作原理和用途,熟悉了这部分,接下来请看下集【项目】webpack+vue---手动搭建项目(下篇:vue搭建配置)

附上本项目github地址:webpack-vue,建议切到feature-webpack分支进行查阅