天天看點

Vue學習筆記

Vue學習筆記

目錄

    • 一、環境搭建
      • 1. Node.js、Npm、Cnpm
      • 2. Vue-cli
    • 二、Vue執行個體
      • 1. 目錄結構
      • 2. 生命周期
      • 3.過濾器
      • 4.計算屬性
      • 5.監聽器
      • 6.方法
    • 三、元件
      • 1. 元件介紹
      • 2. 使用Element UI元件庫
      • 3. 建立自定義元件
    • 四、Router路由
      • 1. Router配置
      • 2. router-link router-view
      • 3. keep-alive
      • 4. 導航守衛
    • 五、方法封裝
      • 1. 全局變量
      • 2. 彈窗封裝
      • 3. 淺拷貝與深拷貝
      • 4. axios封裝
    • 六、Webpack
      • 1. build/build.js
      • 2. build/check-version.js
      • 3. build/utils.js
      • 4. build/vue-loader.conf.js
      • 5. build/webpack.base.conf.js
      • 6. build/webpack.dev.conf.js
      • 7. build/webpack.prod.conf.js
      • 8. config/dev.env.js
      • 9. config/index.js
      • 10. config/prod.env.js
      • 11. 配置代了解決跨域問題

Npm依賴于Node.js,直接下載下傳安裝,并配置環境變量

由于個人比較習慣使用 shift+右鍵 喚起powershell來執行指令,預設powershell不允許執行腳本檔案,需要解除此安全政策

set-ExecutionPolicy RemoteSigned
           

Npm預設安裝位置在C槽,修改預設路徑與檢視Npm配置

npm config set prefix "E:/Npm"   # 配置全局安裝目錄
npm config set cache "E:/Npm/npm_cache"   # 配置緩存目錄
npm config ls   # 檢視配置
           

由于一些已知原因國外網絡較慢,于是選擇使用淘寶的cnpm進行建構

npm install cnpm -g
cnpm install vue
cnpm install --global vue-cli
           

直接建構一個基于webpack的項目,需要進行一些配置

> vue init webpack project-name

? Project name project-name
? Project description A Vue.js project
? Author Czy <[email protected]>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) no

   vue-cli · Generated "project-name".

# Project initialization finished!
# ========================
           

我選擇了手動安裝依賴,但是由于依賴生成的 node_modules 動辄百兆,并且若是出現問題,需要重新建構,由于檔案過多,删除時相當慢,而且個人更傾向于像 Maven 一樣共用依賴,于是使用 mklink 做目錄連結

首先複制 package.json 到某目錄,在此執行安裝 cnpm i,會自動生成 node_modules ,此後在項目檔案夾執行mklink即可,也可以先建好目錄連結再執行 cnpm i (i即install縮寫)

Directory: D:\Project\Library\Modules
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         20/01/09     09:21                node_modules
-a----         20/01/04     14:09             62 mklink.md
-a----         20/01/04     14:07           2707 package.json

PS D:\Project\Library\Modules> npm i
           

在 powershell 中不能執行 mklink ,需要使用CMD進行mklink,然後運作 npm run dev 即可正常啟動項目

C:\Users\Czy\Desktop\project-name>mklink /J node_modules D:\Project\Library\Modules\node_modules
Junction created for node_modules <<===>> D:\Project\Library\Modules\node_modules

C:\Users\Czy\Desktop\project-name>npm run dev
> [email protected] dev C:\Users\Czy\Desktop\project-name
> webpack-dev-server --inline --progress --config build/webpack.dev.conf.js
 10 13 13 13 13% building modules 33/37 modules 4 active ...ct-name\src\components\HelloWorld.vue{ parser: "babylon" } is deprecated; we now treat it as {  14 14 95% emitting
 DONE  Compiled successfully in 14788ms                                                                       1:09:13 PM
 I  Your application is running here: http://localhost:8080
           

vue-cli
   ├── build/                # Webpack 配置目錄
   ├── dist/                 # build 生成的生産環境下的項目
   ├── config/               # Vue基本配置檔案,可以設定監聽端口,打包輸出等
   ├── node_modules/         # 依賴包,即 cnpm i生成的目錄
   ├── src/                  # 源碼目錄(建構應用專注于此目錄)
   │   ├── assets/           # 放置需要經由 Webpack 處理的靜态檔案,通常為樣式類檔案,如CSS,SASS以及一些外部的JS
   │   ├── components/       # 元件目錄
   │   ├── filters/          # 過濾器
   │   ├── store/         # 狀态管理
   │   ├── routes/           # 路由,此處配置項目路由
   │   ├── utils/            # 工具類
   │   ├── views/            # 路由頁面元件
   │   ├── App.vue           # 根元件
   │   └── main.js           # 入口檔案
   ├── index.html            # 首頁,打開頁面後會被Vue注入
   ├── static/               # 放置無需經由 Webpack 處理的靜态檔案,通常放置圖檔類資源
   ├── .babelrc              # Babel 轉碼配置
   ├── .editorconfig         # 代碼格式
   ├── .eslintignore         # ESLint 忽略
   ├── .eslintrc             # ESLint 配置
   ├── .gitignore            # Git 忽略
   ├── package.json          # 本項目的配置資訊,啟動方式
   ├── package-lock.json     # 記錄目前狀态下實際安裝的各個npm package的具體來源和版本号
   └── README.md             # 項目說明
           

Vue學習筆記

過濾器可将資料進行過濾,例如可以在列印表格中将1顯示為OK

//模闆中使用
{{status | statusFilter}} //使用{{ 資料 | 過濾器定義}} 支援鍊式 {{ 資料 | 過濾器定義1 |  過濾器定義2}}

//可以在style中引用
:style="status | colorFilter"

//定義過濾器
export default { 
    filters:{
      statusFilter: function(val){
        switch (val){
          case true : return "OK";
          case false : return "Block";
        }
        return "待擷取";
      },
      colorFilter: function(val){
        switch (val){
          case true : return {'color':'green'};
          case false : return {'color':'red'};
        }
        return {'color':'black'};
      },
    }
}
           

全局過濾器定義,注意當全局過濾器與局部過濾器同名時會加載局部過濾器

Vue.filter('dataFormat', (input, pattern = '') => {});
           

模闆内的表達式非常便利,但是設計它們的初衷是用于簡單運算的。在模闆中放入太多的邏輯會讓模闆過重且難以維護。計算屬性的結果會被緩存,除非依賴的響應式屬性變化才會重新計算,computed擅長處理的場景:一個資料受多個資料影響

模闆: {{message}} 計算屬性: {{reversedMessage }}

export default { 
     computed: { 
        reversedMessage: function () {
          return this.message.split('').reverse().join('')
        }
      }
}
           

監聽資料的改變,一旦資料改變則觸發監聽器,watch擅長處理的場景:一個資料影響多個資料

export default { 
    watch:{
      radio: function(newV,oldV){
        console.log(`監聽radio的值改變: ${oldV} -> ${newV}`);
      }
    },
}
           

響應@click觸發事件

<div  @click='search'>點選</div >

export default { 
    methods: {
      clickHandle: function(e) {
        console.log("我被觸發了",e);
      }
}
           

Vue-cli預設是建構單頁應用,使用Url的錨來确定元件引用,元件是可複用的 Vue 執行個體, 如果網頁中的某一個部分需要在多個場景中使用,那麼我們可以将其抽出為一個元件進行複用。元件大大提高了代碼的複用率。

安裝元件庫

npm i element-ui -S
           

在main.js中引入元件庫

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
           

上述是全局引入,但是由于上邊說了Vue是單頁應用,也就是說明打包起來的項目其實就是一頁,所有需要的資源将全部被打包,是以我個人更傾向于引入部分元件,當然也可以在單頁cdn引入全部的元件,這樣就不會打包Element UI的元件進去了,現在使用本地資源按需引入,則需要借助 babel-plugin-component ,安裝依賴,運作如果提示某檔案夾缺少檔案則添加檔案,如果提示缺少es2015則也要安裝,另外更改檔案.babelrc

cnpm i babel-plugin-component -D
           

.babelrc

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2",
	["es2015", { "modules": false }]
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime",[
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]]
}
           

main.js

import { Menu,MenuItem } from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(Menu)
Vue.use(MenuItem)
           

像這些元件庫一樣在元件内類似作為元素使用,需要一個.vue檔案作為建立的自定義元件,以及index.js暴露接口

index.js

import layout from './layout';
/* istanbul ignore next */
layout.install = function(Vue) {
  Vue.component(layout.name, layout);
};
export default layout;
           

再在main.js引入并挂載即可

import layout from '@/components/common/layout';
Vue.use(layout)
           

//以此配置介紹

import Vue from 'vue'
import Router from 'vue-router'
//引入頁面元件
import Index from "@/components/login/Index.vue"
import ManagerIndex from "@/components/manager/Index.vue"
//挂載Router
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',                        // 頁面路徑 /#/
      name: "Index",                    // 頁面命名,當跳轉頁面需要傳參時就需要使用name
      component: Index,                 // 加載元件
    },
    {
      path: '/ManagerIndex',
      name: "ManagerIndex",
      component: ManagerIndex,
      meta:{                             // 一些配置資訊
        auth: true                       // 此處配置此頁面需要鑒權,需配合router.beforeEach()使用
      },
      children: [                        // 此元件的子元件
        {
          path: 'OverView',              // 頁面路徑/#/ManagerIndex/OverView
          name: "OverView",
          component: OverView
        }
        ]
    }
  ]
})
           

router-link有一個to屬性,即為跳轉錨點用,router-view則是根據錨點來加載Router定義的元件的容器

<router-link :to="/">直接轉到</router-link>
<router-link :to="{ path: '/path', query: { id: 123 }}">query參數</router-link>
<router-link :to="{ name: 'routername', params: { id: 123 }}">param參數,使用name,在path中标明占位符:id</router-link>
<router-view></router-view>
           

router-link為聲明式跳轉,Router提供了程式設計式跳轉

this.$router.push({ name: 'OverView'})
           

當 router-link 進行跳轉時,元件會動态的進行建立銷毀,如果想保持元件狀态,可使用

<keep-alive/>

,注意這樣則不會觸發元件生命周期了

<keep-alive><router-view></router-view></keep-alive>
           

在注冊路由時在meat中聲明了一個auth用來鑒權,需要配合 router.beforeEach() 以及 全局變量 使用,在main.js聲明此方法,進行導航守衛,注意務必調用next()方法,否則不會執行跳轉

router.beforeEach((to, from, next) => {
  if (to.meta.auth && !Vue.prototype.$globalData.user) {
    next({
      path: "/"
    })
  } else {
    next();
  }
})
           

在dispose.js中聲明并暴露出口

const $globalData = {
  user: 0,
  url: "http://dev.touchczy.top/",
  header: {
    'content-type': 'application/x-www-form-urlencoded'
  }
}
export default {
  $globalData: $globalData
}
           

在main.js中引入并拓展Vue原型

import dispose from '@/vector/dispose'
Vue.prototype.$globalData = dispose.$globalData;
           

在dispose.js中引入元件,封裝加載與彈窗,并暴露出口

import {
  Message,
  Loading
} from 'element-ui'

function startLoading(options) {
  if (!options.load) return true;
  var loadingInstance = Loading.service({
    lock: true,
    text: 'loading...'
  })
  return loadingInstance;
}

function endLoading(options, loadingInstance) {
  if (!options.load) return true;
  loadingInstance.close();
}

function toast(msg, type = 'error') {
  Message({
    message: msg,
    type: type,
    duration: 2000,
    center: true
  })
}
export default {
  $toast: toast
}

main.js
import dispose from '@/vector/dispose'
Vue.prototype.$toast = dispose.$toast;
           

function extend() {
  var aLength = arguments.length;
  var options = arguments[0];
  var target = {};
  var copy;
  var i = 1;
  if (typeof options === "boolean" && options === true) {
    //深拷貝 (僅遞歸處理對象)
    for (; i < aLength; i++) {
      if ((options = arguments[i]) != null) {
        if (typeof options !== 'object') {
          return options;
        }
        for (var name in options) {
          copy = options[name];
          if (target === copy) {
            continue;
          }
          target[name] = this.extend(true, options[name]);
        }
      }
    }
  } else {
    //淺拷貝
    target = options;
    if (aLength === i) {
      target = this;
      i--;
    } //如果是隻有一個參數,拓展功能 如果兩個以上參數,将後續對象加入到第一個對象
    for (; i < aLength; i++) {
      options = arguments[i];
      for (var name in options) {
        target[name] = options[name];
      }
    }
  }
  return target;
}
           

由于剛開始學習Vue,對于網絡請求還是更傾向于使用 success fail complete 來寫

封裝了請求時加載Loading,請求失敗彈窗,并且傳回promise對象可以繼續使用then等

當發起post請求時使用{ 'content-type': 'application/x-www-form-urlencoded'}作為請求頭,在transformRequest将json形請求轉化為表單請求

function ajax(requestInfo) {
  var options = {
    load: true,
    url: "",
    method: "GET",
    data: {},
    param: {},
    success: () => {},
    fail: function() { this.completeLoad = () => {toast("伺服器錯誤", 'error');}
    },
    complete: () => {},
    completeLoad: () => {}
  };
  extend(options, requestInfo);
  let loadingInstance = startLoading(options);
  return axios.request({
    url: options.url,
    data: options.data,
    params: options.param,
    method: options.method,
    headers: $globalData.header,
    transformRequest: [function(data) {
      let ret = ''
      for (let it in data) ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
      return ret
    }]
  }).then(function(res) {
    try {
      options.success(res);
    } catch (e) {
      options.completeLoad = () => {
        toast("PARSE ERROR");
      }
      console.warn(e);
    }
  }).catch(function(res) {
    options.fail(res);
  }).then(function(res) {
    endLoading(options, loadingInstance);
    try {
      options.complete(res);
    } catch (e) {
      console.warn(e);
    }
    options.completeLoad(res);
  })
}

export default {
  $ajax: ajax
}
           
import dispose from '@/vector/dispose'
Vue.prototype.$ajax = dispose.$ajax;
           

由于測試必須處理跨域請求,并且需要使用cookies時,首先在聲明 axios.defaults.withCredentials = true ,此時在後端就不能将Access-Control-Allow-Origin設定為*了

axios

axios.defaults.withCredentials = true
           

PHP

header('Content-Type: text/html;charset=utf-8');
header('Access-Control-Allow-Origin:http://localhost:8080'); // 允許網址請求
header('Access-Control-Allow-Methods:POST,GET,OPTIONS,DELETE'); // 允許請求的類型
header('Access-Control-Allow-Credentials: true'); // 設定是否允許發送 cookies
header('Access-Control-Allow-Headers: Content-Type,Content-Length,Accept-Encoding,X-Requested-with, Origin'); // 設定允許自定義請求頭的字段 
           

// 建構生産版本 node build/build.js
require('./check-versions')()   //check-versions 調用檢查版本的檔案 并直接調用該函數
process.env.NODE_ENV = 'production'   // 注冊到window的全局變量,可以用以區分生産環境和開發環境,此為生産環境
const ora = require('ora')   // 終端顯示的轉輪loading
const rm = require('rimraf')   // node環境下rm -rf的指令庫
const path = require('path')   // 檔案路徑處理庫
const chalk = require('chalk')   // 終端顯示帶顔色的文字
const webpack = require('webpack')   // webpack
const config = require('../config')   // 引入配置
const webpackConfig = require('./webpack.prod.conf')   // 引入生産環境下配置

const spinner = ora('building for production...')   // 終端顯示正在建構
spinner.start()   // 終端顯示loading

rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {   // 删除已編譯檔案,即dist檔案夾
  if (err) throw err
  webpack(webpackConfig, (err, stats) => {   //在删除完成的回調函數中開始編譯
    spinner.stop()   // 終端終止loading
    if (err) throw err
    process.stdout.write(stats.toString({   // 在編譯完成的回調函數中,在終端輸出編譯的檔案
      colors: true,
      modules: false,
      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
      chunks: false,
      chunkModules: false
    }) + '\n\n')
    /* ... */ // 以下為編譯結果的輸出
  })
})
           

const chalk = require('chalk')   // 終端顯示帶顔色的文字
const semver = require('semver') // 對版本進行檢查
const packageConfig = require('../package.json') // 讀取項目配置檔案
const shell = require('shelljs') // shell

function exec (cmd) {   //傳回通過child_process子產品的建立子程序,執行 Unix 系統指令後轉成沒有空格的字元串
  return require('child_process').execSync(cmd).toString().trim()
}

const versionRequirements = [
  {
    name: 'node',
    currentVersion: semver.clean(process.version),   // 使用semver格式化版本
    versionRequirement: packageConfig.engines.node   // 擷取package.json中設定的node版本
  }
]

if (shell.which('npm')) {
  versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),   // 調用npm --version指令,并且把參數傳回給exec函數,進而擷取純淨的版本号
    versionRequirement: packageConfig.engines.npm
  })
}

module.exports = function () {
  /* ... */   //警告或者錯誤提示
}
           

// 處理css
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')

exports.assetsPath = function (_path) {   //導出檔案的位置,根據環境判斷開發環境和生産環境,為config檔案中index.js檔案中定義的build.assetsSubDirectory或dev.assetsSubDirectory
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
  return path.posix.join(assetsSubDirectory, _path) //path 子產品提供了一些用于處理檔案路徑的工具
}

exports.cssLoaders = function (options) {
  options = options || {}
  const cssLoader = { //使用了css-loader和postcssLoader,通過options.usePostCSS屬性來判斷是否使用postcssLoader中壓縮等方法
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  const postcssLoader = {
    loader: 'postcss-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }
  function generateLoaders (loader, loaderOptions) {
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {   //Object.assign是es6文法的淺拷貝,後兩者合并後複制完成指派
          sourceMap: options.sourceMap
        })
      })
    }
    
    if (options.extract) {
      return ExtractTextPlugin.extract({   //ExtractTextPlugin可提取出文本,代表首先使用上面處理的loaders,當未能正确引入時使用vue-style-loader
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)   //傳回vue-style-loader連接配接loaders的最終值
    }
  }
  return {
    css: generateLoaders(),//需要css-loader 和 vue-style-loader
    postcss: generateLoaders(),//需要css-loader和postcssLoader  和 vue-style-loader
    less: generateLoaders('less'),//需要less-loader 和 vue-style-loader
    sass: generateLoaders('sass', { indentedSyntax: true }),//需要sass-loader 和 vue-style-loader
    scss: generateLoaders('sass'),//需要sass-loader 和 vue-style-loader
    stylus: generateLoaders('stylus'),//需要stylus-loader 和 vue-style-loader
    styl: generateLoaders('stylus')//需要stylus-loader 和 vue-style-loader
  }
}
exports.styleLoaders = function (options) {
  const output = []
  const loaders = exports.cssLoaders(options)
  for (const extension in loaders) {  //将各種css,less,sass等綜合在一起得出結果輸出output
    const loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }

  return output
}

exports.createNotifierCallback = () => {
  const notifier = require('node-notifier') //發送跨平台通知系統
  return (severity, errors) => {
    if (severity !== 'error') return
    const error = errors[0] 
    const filename = error.file && error.file.split('!').pop()
    notifier.notify({   //當報錯時輸出錯誤資訊的标題,錯誤資訊詳情,副标題以及圖示
      title: packageConfig.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      icon: path.join(__dirname, 'logo.png')   // 用于連接配接路徑,會正确使用目前系統的路徑分隔符,Unix系統是"/",Windows系統是""
    })
  }
}
           

// 處理.vue檔案,解析這個檔案中的每個語言塊(template、script、style),轉換成js可用的js子產品
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
  ? config.build.productionSourceMap
  : config.dev.cssSourceMap
  
module.exports = {   // 處理項目中的css檔案,生産環境和測試環境預設是打開sourceMap,而extract中的提取樣式到單獨檔案隻有在生産環境中才需要
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    extract: isProduction
  }),
  cssSourceMap: sourceMapEnabled,
  cacheBusting: config.dev.cacheBusting,
  transformToRequire: {   // 在模版編譯過程中,編譯器可以将某些屬性,如 src 路徑,轉換為require調用,以便目标資源可以由 webpack 處理
    video: ['src', 'poster'],
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}
           

// 開發和生産共同使用提出來的基礎配置檔案,主要實作配制入口,配置輸出環境,配置子產品resolve和插件等
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')

function resolve (dir) {   // 拼接出絕對路徑
  return path.join(__dirname, '..', dir)
}

module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {   // 入口檔案,可以有多個入口,也可隻有一個,預設為單頁面是以隻有app一個入口
    app: './src/main.js'
  },
  output: {    //配置出口,預設是/dist作為目标檔案夾的路徑
    path: config.build.assetsRoot,   // 路徑
    filename: '[name].js',   // 輸出檔案名
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],  // 自動的擴充字尾,比如一個js檔案,則引用時書寫可不要寫.js
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),   // @ 即為 /src
    }
  },
  module: {   // 使用插件配置相應檔案的處理方法
    rules: [{   
        test: /\.vue$/,
        loader: 'vue-loader',   // 使用vue-loader将vue檔案轉化成js的子產品
        options: vueLoaderConfig
      },{
        test: /\.js$/,
        loader: 'babel-loader',   // js檔案需要通過babel-loader進行編譯成es5檔案以及壓縮等操作
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },{
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',   // 圖檔、音像、字型都使用url-loader進行處理,超過10000會編譯成base64
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },{
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },{
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  node: {   //以下選項是Node.js全局變量或子產品,這裡主要是防止webpack注入一些Node.js的東西到vue中
    setImmediate: false,
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}
           

// 開發環境的wepack相關配置
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')   // 通過webpack-merge實作webpack.dev.conf.js對wepack.base.config.js的繼承
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')   // 美化webpack的錯誤資訊和日志的插件
const portfinder = require('portfinder')   // 檢視空閑端口位置,預設情況下搜尋8000這個端口

const HOST = process.env.HOST   //processs為node的一個全局對象擷取目前程式的環境變量,即HOST
const PORT = process.env.PORT && Number(process.env.PORT)

const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })   // 規則是工具utils中處理出來的styleLoaders,生成了css,less,postcss等規則
  },
  devtool: config.dev.devtool,   // 增強調試

  devServer: {   // 此處的配置都是在config的index.js中設定好了
    clientLogLevel: 'warning',
    historyApiFallback: {   // 當使用 HTML5 History API 時,任意的 404 響應都可能需要被替代為 index.html
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    hot: true,   // 熱加載
    contentBase: false, 
    compress: true,   // 壓縮
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    open: config.dev.autoOpenBrowser,   // 調試時自動打開浏覽器
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,   // 接口代理
    quiet: true,   // 控制台是否禁止列印警告和錯誤,若用FriendlyErrorsPlugin 此處為 true
    watchOptions: {
      poll: config.dev.poll,   // 檔案系統檢測改動
    }
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(),   // 子產品熱替換插件,修改子產品時不需要重新整理頁面
    new webpack.NamedModulesPlugin(),   // 顯示檔案的正确名字
    new webpack.NoEmitOnErrorsPlugin(),   // 當webpack編譯錯誤的時候,來中端打包程序,防止錯誤代碼打包到檔案中
    new HtmlWebpackPlugin({   // 該插件可自動生成一個 html5 檔案或使用模闆檔案将編譯好的代碼注入進去
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    new CopyWebpackPlugin([   // 複制插件
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port
  portfinder.getPort((err, port) => {   // 查找端口号
    if (err) {
      reject(err)
    } else {   // 端口被占用時就重新設定evn和devServer的端口
      process.env.PORT = port
      devWebpackConfig.devServer.port = port
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        compilationSuccessInfo: {
          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors
        ? utils.createNotifierCallback()
        : undefined
      }))
      resolve(devWebpackConfig)
    }
  })
})
           

// 生産環境的wepack相關配置檔案
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')   // webpack 配置合并插件
const baseWebpackConfig = require('./webpack.base.conf')   // webpack 基本配置
const CopyWebpackPlugin = require('copy-webpack-plugin')   // webpack 複制檔案和檔案夾的插件
const HtmlWebpackPlugin = require('html-webpack-plugin')   // 自動生成 html 并且注入到 .html 檔案中的插件
const ExtractTextPlugin = require('extract-text-webpack-plugin')   // 提取css的插件
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')   // webpack 優化壓縮和優化 css 的插件
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const env = require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({   
      sourceMap: config.build.productionSourceMap,   // 開啟調試的模式。預設為true
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {   // 壓縮
          warnings: false
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    new ExtractTextPlugin({   // 抽取文本,比如打包之後的index頁面有style插入,就是這個插件抽取出來的,減少請求
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      allChunks: true,
    }),
    new OptimizeCSSPlugin({   // 優化css的插件
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
    new HtmlWebpackPlugin({   // html打包
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,   // 删除注釋
        collapseWhitespace: true,   // 删除空格
        removeAttributeQuotes: true   // 删除屬性的引号
      },
      chunksSortMode: 'dependency' // 子產品排序,按照我們需要的順序排序
    }),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new webpack.optimize.CommonsChunkPlugin({   // 抽取公共的子產品
      name: 'vendor',
      minChunks (module) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),

    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),

    new CopyWebpackPlugin([   // 複制,比如打包完之後需要把打包的檔案複制到dist裡面
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')
  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}
if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
           

// config内的檔案是服務于build的
const merge = require('webpack-merge')   // webpack-merge提供了一個合并函數,它将數組和合并對象建立一個新對象,遇到函數,執行它們,将傳回的值封裝在函數中,這邊将dev和prod進行合并
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
})
           

// 配置檔案是用來定義開發環境和生産環境中所需要的參數
const path = require('path')
module.exports = {
  dev: {   // 開發環境下面的配置
    assetsSubDirectory: 'static',   // 子目錄,一般存放css,js,image等檔案
    assetsPublicPath: '/',   // 根目錄
    proxyTable: {},   // 可利用該屬性解決跨域的問題
    host: 'localhost', // 服務啟動位址
    port: 8080, // 服務啟動端口
    autoOpenBrowser: false,   // 是否自動打開浏覽器
    errorOverlay: true,   // 浏覽器錯誤提示
    notifyOnErrors: true,   // 跨平台錯誤提示
    poll: false,  // 使用檔案系統(file system)擷取檔案改動的通知devServer.watchOptions
    devtool: 'cheap-module-eval-source-map',   // 增加調試,該屬性為原始源代碼(僅限行)不可在生産環境中使用
    cacheBusting: true,   // 使緩存失效
    cssSourceMap: true   // 代碼壓縮後進行調bug定位将非常困難,于是引入sourcemap記錄壓縮前後的位置資訊記錄,當産生錯誤時直接定位到未壓縮前的位置
  },

  build: {    // 生産環境下面的配置
    index: path.resolve(__dirname, '../dist/index.html'),   // index編譯後生成的位置和名字,根據需要改變字尾,比如index.php
    assetsRoot: path.resolve(__dirname, '../dist'),   // 編譯後存放生成環境代碼的位置
    assetsSubDirectory: 'public/vue-app/index',   // js,css,images等存放檔案夾名
    assetsPublicPath: '/',   // 釋出的根目錄為Web容器絕對路徑,修改為./則為相對路徑
    productionSourceMap: true,
    devtool: '#source-map',
    productionGzip: false,   //unit的gzip指令用來壓縮檔案,gzip模式下需要壓縮的檔案的擴充名有js和css
    productionGzipExtensions: ['js', 'css'],
    bundleAnalyzerReport: process.env.npm_config_report
  }
}
           

// 釋出時調用prod.env.js的生産環境配置
module.exports = {
  NODE_ENV: '"production"'
}
           

// 在 config/index.js 中的 proxyTable 配置
proxyTable: {
  '/':{
    target: "http://www.xxx.com",   // 要通路的位址
    changeOrigin: true   // 開啟跨域
  }
}
// 配置了解為将通路 / 代理為 http://www.xxx.com 這樣就不會觸發跨域問題,即不會被浏覽器攔截請求
// 在Vue的請求中要通路 http://www.xxx.com/testData 接口直接寫成對 /testData 請求即可
// 通過 process.env.NODE_ENV 判斷開發環境與生産環境差別請求 Url
           
上一篇: EFS
下一篇: 系統備份