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 # 項目說明
過濾器可将資料進行過濾,例如可以在列印表格中将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