深入淺出的webpack構建工具--webpack4+vue搭建環境 (十三)

龍恩0707發表於2018-09-24

深入淺出的webpack構建工具--webpack4+vue搭建環境 (十三)

從上面一系列的webpack配置的學習,我們現在來使用webpack來搭建vue的開發環境。首先我們來設想下我們的專案的目錄結構如下:

### 目錄結構如下:
demo1                                       # 工程名
|   |--- dist                               # 打包後生成的目錄檔案             
|   |--- node_modules                       # 所有的依賴包
|   |--- app
|   | |---index
|   | | |-- views                           # 存放所有vue頁面檔案
|   | | |-- components                      # 存放vue公用的元件
|   | | |-- app.js                          # vue入口配置檔案
|   |--- views
|   | |-- index.html                        # html檔案
|   |--- webpack.config.js                  # webpack配置檔案 
|   |--- .gitignore  
|   |--- README.md
|   |--- package.json
|   |--- .babelrc                           # babel轉碼檔案

因此需要依賴package.json檔案配置如下:

{
  "name": "vue專案架構",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --progress --colors --devtool cheap-module-eval-source-map --hot --inline",
    "build": "webpack --progress --colors --devtool cheap-module-source-map",
    "build:dll": "webpack --config webpack.dll.config.js"
  },
  "author": "tugenhua0707@qq.com",
  "sideEffects": false,
  "license": "ISC",
  "devDependencies": {
    "add-asset-html-webpack-plugin": "^2.1.3",
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-stage-2": "^6.24.1",
    "clean-webpack-plugin": "^0.1.19",
    "css-loader": "^1.0.0",
    "cssnano": "^4.0.5",
    "extract-text-webpack-plugin": "^4.0.0-beta.0",
    "file-loader": "^1.1.11",
    "happypack": "^5.0.0",
    "html-webpack-plugin": "^3.2.0",
    "lodash-es": "^4.17.11",
    "mini-css-extract-plugin": "^0.4.2",
    "path": "^0.12.7",
    "postcss-cssnext": "^3.1.0",
    "postcss-loader": "^3.0.0",
    "postcss-pxtorem": "^4.0.1",
    "postcss-sprites": "^4.2.1",
    "style-loader": "^0.21.0",
    "stylus": "^0.54.5",
    "stylus-loader": "^3.0.2",
    "uglifyjs-webpack-plugin": "^1.2.7",
    "url-loader": "^1.0.1",
    "vue-loader": "^15.4.2",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.5.17",
    "webpack": "^4.16.1",
    "webpack-cli": "^3.0.8",
    "webpack-deep-scope-plugin": "^1.6.0",
    "webpack-dev-server": "^3.1.4",
    "webpack-parallel-uglify-plugin": "^1.1.0"
  },
  "dependencies": {
    
  }
}

接著專案 views/index.html 程式碼初始化如下:

<!DOCTYPE html>
<html>
<head>
  <title>webpack4+vue專案架構</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
</head>
<body>
  <div id="app">
  </div>

</body>
</html>

.babelrc 轉碼檔案程式碼如下:

{
  "plugins": [
     [
      "transform-runtime",
      {
        "polyfill": false
      }
     ]
   ],
   "presets": [
     [
       "env",
       {
         "modules": false   // 關閉Babel的模組轉換功能,保留ES6模組化語法
       }
     ],
     "stage-2"
  ]
}

我們現在在 app/index/views 下新建一個test.vue 程式碼如下:

<style lang="stylus">
  
</style>

<template>
  <div class='app-container'>
    <div>
      <p v-if="datas.length > 0" v-for="(item, index) in datas">{{item}}</p>
    </div>
  </div>
</template>

<script type="text/javascript">
  export default {
    data() {
      return {
        datas: [1, 2, 3, 4]
      }
    }
  }
</script>

如上程式碼檔案是vue檔案,因此我們需要安裝vue-loader等外掛,安裝命令如下:

npm i -D vue-loader css-loader vue-template-compiler vue-style-loader

vue框架執行需要的庫,命令如下:

npm i --save vue

上面依賴的作用如下:
vue-loader: 解析和轉換.vue檔案,提取出其中的邏輯程式碼script,樣式程式碼style及html模板template,再分別將他們交給對應的Loader去處理。

css-loader: 載入由vue-loader提取出的css程式碼。

vue-template-compiler: 將vue-loader 提取出的HTML模板編譯成對應的可執行javascript程式碼。

然後我們編寫下 app/index/app.js 的入口檔案簡單的程式碼如下:

import Vue from 'vue';

import Test from './views/test';

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

接著 webpack.config.js 程式碼配置如下:

const path = require('path');

// 引入 mini-css-extract-plugin 外掛 
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// 清除dist目錄下的檔案
const ClearWebpackPlugin = require('clean-webpack-plugin');

const webpack = require('webpack');

// 引入打包html檔案
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 引入HappyPack外掛 
const HappyPack = require('happypack');

// 引入 ParallelUglifyPlugin 外掛
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

// 引入 webpack-deep-scope-plugin 優化
const WebpackDeepScopeAnalysisPlugin = require('webpack-deep-scope-plugin').default;

module.exports = {
  // 入口檔案
  entry: {
    main: './app/index/app.js'
  },
  output: {
    filename: process.env.NODE_ENV === 'production' ? '[name].[contenthash].js' : 'bundle.js',
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        // 使用正則去匹配
        test: /\.styl$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {}
          },
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: [
                require('postcss-cssnext')(),
                require('cssnano')(),
                require('postcss-pxtorem')({
                  rootValue: 16,
                  unitPrecision: 5,
                  propWhiteList: []
                }),
                require('postcss-sprites')()
              ]
            }
          },
          {
            loader: 'stylus-loader',
            options: {}
          }
        ]
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'happypack/loader?id=css-pack'
        ]
      },
      {
        test: /\.(png|jpg)$/,
        use: ['happypack/loader?id=image']
      },
      {
        test: /\.js$/,
        // 將對.js檔案的處理轉交給id為babel的HappyPack的實列
        use: ['happypack/loader?id=babel'],
        // loader: 'babel-loader',
        exclude: path.resolve(__dirname, 'node_modules') // 排除檔案
      },
      {
        test: /\.vue$/,
        use: ['happypack/loader?id=vue-loader'],
        exclude: path.resolve(__dirname, 'node_modules') // 排除檔案
      }
    ]
  },
  resolve: {
    extensions: ['*', '.js', '.json', '.vue']
  },
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    port: 8081,
    host: '0.0.0.0',
    headers: {
      'X-foo': '112233'
    },
    inline: true,
    overlay: true,
    stats: 'errors-only'
  },
  mode: 'development', // 開發環境下
  // mode: 'production',
  plugins: [
    new HtmlWebpackPlugin({
      template: './views/index.html' // 模版檔案
    }),
    new ClearWebpackPlugin(['dist']),

    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css'
    }),
    /****   使用HappyPack例項化    *****/
    new HappyPack({
      // 用唯一的識別符號id來代表當前的HappyPack 處理一類特定的檔案
      id: 'babel',
      // 如何處理.js檔案,用法和Loader配置是一樣的
      loaders: ['babel-loader']
    }),
    new HappyPack({
      // 用唯一的識別符號id來代表當前的HappyPack 處理一類特定的檔案
      id: 'vue-loader',
      // 如何處理.js檔案,用法和Loader配置是一樣的
      loaders: ['vue-loader']
    }),
    new HappyPack({
      id: 'image',
      loaders: [{
        loader: require.resolve('url-loader'),
        options: {
          limit: 10000,
          name: '[name].[ext]'
        }
      }]
    }),
    // 處理styl檔案
    new HappyPack({
      id: 'css-pack',
      loaders: ['css-loader']
    }),
    // 使用 ParallelUglifyPlugin 並行壓縮輸出JS程式碼
    new ParallelUglifyPlugin({
      // 傳遞給 UglifyJS的引數如下:
      uglifyJS: {
        output: {
          /*
           是否輸出可讀性較強的程式碼,即會保留空格和製表符,預設為輸出,為了達到更好的壓縮效果,
           可以設定為false
          */
          beautify: false,
          /*
           是否保留程式碼中的註釋,預設為保留,為了達到更好的壓縮效果,可以設定為false
          */
          comments: false
        },
        compress: {
          /*
           是否在UglifyJS刪除沒有用到的程式碼時輸出警告資訊,預設為輸出,可以設定為false關閉這些作用
           不大的警告
          */
          warnings: false,

          /*
           是否刪除程式碼中所有的console語句,預設為不刪除,開啟後,會刪除所有的console語句
          */
          drop_console: true,

          /*
           是否內嵌雖然已經定義了,但是隻用到一次的變數,比如將 var x = 1; y = x, 轉換成 y = 5, 預設為不
           轉換,為了達到更好的壓縮效果,可以設定為false
          */
          collapse_vars: true,

          /*
           是否提取出現了多次但是沒有定義成變數去引用的靜態值,比如將 x = 'xxx'; y = 'xxx'  轉換成
           var a = 'xxxx'; x = a; y = a; 預設為不轉換,為了達到更好的壓縮效果,可以設定為false
          */
          reduce_vars: true
        }
      }
    }),
    new WebpackDeepScopeAnalysisPlugin()
  ]
};

然後我們執行 npm run dev 後,打包報錯了:如下:

vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.

通過百度搜尋後,網上都說需要引入 VueLoaderPlugin 的webpack元件,webpack如下引入方式:

const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
  plugins: [
    new VueLoaderPlugin()
  ]
}

引入後,再執行還會報錯,如下資訊:

Error: [VueLoaderPlugin Error] No matching use for vue-loader is found.
Make sure the rule matching .vue files include vue-loader in its use.

我這邊是使用的是"vue-loader": "^15.4.2", 我剛開始以為是 vue-loader 版本的問題了,然後當我修改版本到14版本後,還是會報錯,然後在github上搜尋這個答案,發現老外也提了這樣的問題,說是不是版本的問題,最後尤雨溪回答,這和vue-loader版本沒有關係,最後我搜尋到 vue-loader 15.1, 它不支援happypack這個外掛優化,可以看github(https://github.com/vuejs/vue-loader/issues/1339)上的提示,有了這個提示,我直接把webpack中和vue相關的 happypack的優化去掉,然後打包既然就可以了。
因此webpack中的vue配置就變成如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: ['vue-loader']
      }
    ]
  }
}

一切準備就緒後,我們現在執行 npm run dev 後,會執行如下所示:

然後我們就可以在瀏覽器下 執行 http://0.0.0.0:8081/ 就可以看到vue頁面了,當我們繼續修改 test.vue的時候, 儲存後更新。

理想是美好的,現實是殘酷的,但是當我一切認為可以的時候,然後我們把對應的<style lang="stylus">樣式加上,如下程式碼:

<style lang="stylus" >
  .app-container
    width 200px
</style>
<template>
  <div class='app-container'>
    <div>
      <p v-if="datas.length > 0" v-for="(item, index) in datas">{{item}}</p>
    </div>
  </div>
</template>

<script type="text/javascript">
  export default {
    data() {
      return {
        datas: [5, 2, 3, 4]
      }
    }
  }
</script>

繼續打包,就報這樣的錯誤了;如下:

Module parse failed: Unexpected token (2:0)
You may need an appropriate loader to handle this file type.
| 
> .app-container
|   width 200px

最後github搜尋,發現 webpack4 不相容vue-loader 15.x.x版本,github點選檢視(https://github.com/airyland/vux/issues/3060)或者可以看這個(https://segmentfault.com/a/1190000014586699

然後我把vue-loader 改成 "vue-loader": "^14.2.2",然後繼續打包, 就沒有問題了。
如下執行結果:

 

二:webpack4上如何提取css檔案到單獨的檔案

webpack4以上貌似不能使用mini-css-extract-plugin提取css檔案,比如我在程式碼裡面這樣寫:

new MiniCssExtractPlugin({
  filename: process.env.NODE_ENV === 'production' ? 'css/[name].[contenthash:8].css' : '[name].css',
  chunkFilename: process.env.NODE_ENV === 'production' ? 'css/[id].[contenthash:8].css' : '[id].css'
}),

但是貌似提取不了,看到說webpack4還是可以用extract-text-webpack-plugin 只不過安裝的時候外掛名加個@next,於是就改用
extract-text-webpack-plugin來提取這個外掛就可以打包在一個css檔案內.

因此我們現在安裝命令如下:

npm i extract-text-webpack-plugin@next -D

然後在webpack.config.js 這樣配置程式碼即可:

// webpack.config.js
var ExtractTextPlugin = require("extract-text-webpack-plugin")

module.exports = {
  // other options...
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          extractCSS: true
        }
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("style.css")
  ]
}

執行結構如下所示;可以看到css檔案被提取出來了。

具體可以看官方文件(https://vue-loader-v14.vuejs.org/zh-cn/configurations/extract-css.html)

下面是github上的原始碼:

基於webpack4+vue 單頁面(https://github.com/tugenhua0707/webpack-all-demo/tree/master/webpack%2Bvue)

相關文章