這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 平時大家開發vue項目的時候,相信大部分人都是使用 vue-cli腳手架生成的項目架構,然後 npm run install 安裝依賴,npm run serve啟動項目然後就開始寫業務代碼了。 但是對項目里的webpack封裝和配置瞭解 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
平時大家開發vue項目的時候,相信大部分人都是使用 vue-cli
腳手架生成的項目架構,然後 npm run install
安裝依賴,npm run serve
啟動項目然後就開始寫業務代碼了。
但是對項目里的webpack
封裝和配置瞭解的不清楚,容易導致出問題不知如何解決,或者不會通過webpack
去擴展新功能。
該篇文章主要是想告訴兄弟們,如何一步一步的通過 webpack4
來搭建自己的vue
開發環境
首先我們要知道 vue-cli
生成的項目,幫我們配置好了哪些功能?
ES6
代碼轉換成ES5
代碼scss/sass/less/stylus
轉css
.vue
文件轉換成js
文件- 使用
jpg
、png
,font
等資源文件 - 自動添加css各瀏覽器產商的首碼
- 代碼熱更新
- 資源預載入
- 每次構建代碼清除之前生成的代碼
- 定義環境變數
- 區分開發環境打包跟生產環境打包
- ....
1. 搭建 webpack
基本環境
該篇文章並不會細講 webpack
是什麼東西,如果還不是很清楚的話,可以先去看看 webpack官網
簡單的說,webpack
是一個模塊打包機,可以分析你的項目依賴的模塊以及一些瀏覽器不能直接運行的語言jsx
、vue
等轉換成 js
、css
文件等,供瀏覽器使用。
1.1 初始化項目
在命令行中執行 npm init
然後一路回車就行了,主要是生成一些項目基本信息。最後會生成一個 package.json
文件
npm init
1.2 安裝webpack
1.3 寫點小代碼測試一下webpack
是否安裝成功了
新建一個src
文件夾,然後再建一個main.js
文件
// src/main.js console.log('hello webpack')
然後在 package.json 下麵加一個腳本命令
然後運行該命令
npm run serve
如果在 dist 目錄下生成了一個main.js
文件,則表示webpack
工作正常
2. 開始配置功能
- 新建一個
build
文件夾,用來存放webpack
配置相關的文件 - 在
build
文件夾下新建一個webpack.config.js
,配置webpack
的基本配置 - 修改
webpack.config.js
配置
- 修改
package.json
文件,將之前添加的serve
修改為
"serve": "webpack ./src/main.js --config ./build/webpack.config.js"
2.1 配置 ES6/7/8
轉 ES5
代碼
- 安裝相關依賴
npm install babel-loader @babel/core @babel/preset-env
- 修改
webpack.config.js
配置
- 在項目根目錄添加一個
babel.config.js
文件
- 然後執行
npm run serve
命令,可以看到 ES6代碼被轉成了ES5代碼了
2.1.1 ES6/7/8 Api
轉es5
babel-loader
只會將 ES6/7/8語法轉換為ES5語法,但是對新api並不會轉換。
我們可以通過 babel-polyfill 對一些不支持新語法的客戶端提供新語法的實現
- 安裝
npm install @babel/polyfill
- 修改
webpack.config.js
配置
在 entry
中添加 @babel-polyfill
2.1.2 按需引入polyfill
2.1.2 和 2.1.1 只需要配置一個就行
修改時間 2019-05-05、 來自評論區 兮漫天 的提醒
- 安裝相關依賴
npm install core-js@2 @babel/runtime-corejs2 -S
- 修改 babel-config.js
配置了按需引入 polyfill
後,用到es6
以上的函數,babel
會自動導入相關的polyfill
,這樣能大大減少 打包編譯後的體積
2.2 配置 scss
轉 css
在沒配置 css
相關的 loader
時,引入scss
、css
相關文件打包的話,會報錯
- 安裝相關依賴
npm install sass-loader dart-sass css-loader style-loader -D
sass-loader
, dart-sass
主要是將 scss/sass 語法轉為css
css-loader
主要是解析 css 文件
style-loader
主要是將 css 解析到 html
頁面 的 style
上
- 修改
webpack.config.js
配置
2.3 配置 postcss 實現自動添加css3首碼
- 安裝相關依賴
npm install postcss-loader autoprefixer -D
- 修改
webpack.config.js
配置
- 在項目根目錄下新建一個
postcss.config.js
2.3 使用 html-webpack-plugin
來創建html頁面
使用 html-webpack-plugin
來創建html頁面,並自動引入打包生成的js
文件
- 安裝依賴
npm install html-webpack-plugin -D
- 新建一個 public/index.html 頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id="app"></div> </body> </html>
- 修改
webpack-config.js
配置
2.4 配置 devServer 熱更新功能
通過代碼的熱更新功能,我們可以實現不刷新頁面的情況下,更新我們的頁面
- 安裝依賴
npm install webpack-dev-server -D
- 修改
webpack.config.js
配置
通過配置 devServer
和 HotModuleReplacementPlugin
插件來實現熱更新
2.5 配置 webpack 打包 圖片、媒體、字體等文件
- 安裝依賴
npm install file-loader url-loader -D
file-loader
解析文件url,並將文件複製到輸出的目錄中
url-loader
功能與 file-loader
類似,如果文件小於限制的大小。則會返回 base64
編碼,否則使用 file-loader
將文件複製到輸出的目錄中
- 修改
webpack-config.js
配置 添加rules
配置,分別對 圖片,媒體,字體文件進行配置
// build/webpack.config.js const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const webpack = require('webpack') module.exports = { // 省略其它配置 ... module: { rules: [ // ... { test: /\.(jpe?g|png|gif)$/i, use: [ { loader: 'url-loader', options: { limit: 4096, fallback: { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]' } } } } ] }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, use: [ { loader: 'url-loader', options: { limit: 4096, fallback: { loader: 'file-loader', options: { name: 'media/[name].[hash:8].[ext]' } } } } ] }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, use: [ { loader: 'url-loader', options: { limit: 4096, fallback: { loader: 'file-loader', options: { name: 'fonts/[name].[hash:8].[ext]' } } } } ] }, ] }, plugins: [ // ... ] }
3. 讓 webpack
識別 .vue
文件
- 安裝需要的依賴文件
npm install vue-loader vue-template-compiler cache-loader thread-loader -D npm install vue -S
vue-loader
用於解析.vue
文件
vue-template-compiler
用於編譯模板
cache-loader
用於緩存loader
編譯的結果
thread-loader
使用 worker
池來運行loader
,每個 worker
都是一個 node.js
進程。
- 修改
webpack.config.js
配置
// build/webpack.config.js const path = require('path') const webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin') const VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { // 指定打包模式 mode: 'development', entry: { // ... }, output: { // ... }, devServer: { // ... }, resolve: { alias: { vue$: 'vue/dist/vue.runtime.esm.js' }, }, module: { rules: [ { test: /\.vue$/, use: [ { loader: 'cache-loader' }, { loader: 'thread-loader' }, { loader: 'vue-loader', options: { compilerOptions: { preserveWhitespace: false }, } } ] }, { test: /\.jsx?$/, use: [ { loader: 'cache-loader' }, { loader: 'thread-loader' }, { loader: 'babel-loader' } ] }, // ... ] }, plugins: [ // ... new VueLoaderPlugin() ] }
- 測試一下
- 在 src 新建一個 App.vue
// src/App.vue <template> <div class="App"> Hello World </div> </template> <script> export default { name: 'App', data() { return {}; } }; </script> <style lang="scss" scoped> .App { color: skyblue; } </style>
- 修改
main.js
import Vue from 'vue' import App from './App.vue' new Vue({ render: h => h(App) }).$mount('#app')
- 運行一下
npm run serve
4. 定義環境變數
通過 webpack
提供的DefinePlugin
插件,可以很方便的定義環境變數
plugins: [ new webpack.DefinePlugin({ 'process.env': { VUE_APP_BASE_URL: JSON.stringify('http://localhost:3000') } }), ]
5. 區分生產環境和開發環境
新建兩個文件
-
webpack.dev.js
開發環境使用 -
webpack.prod.js
生產環境使用 -
webpack.config.js
公用配置 -
開發環境與生產環境的不同
5.1 開發環境
- 不需要壓縮代碼
- 需要熱更新
- css不需要提取到css文件
- sourceMap
- ...
5.2 生產環境
- 壓縮代碼
- 不需要熱更新
- 提取css,壓縮css文件
- sourceMap
- 構建前清除上一次構建的內容
- ...
- 安裝所需依賴
npm i @intervolga/optimize-cssnano-plugin mini-css-extract-plugin clean-webpack-plugin webpack-merge copy-webpack-plugin -D
@intervolga/optimize-cssnano-plugin
用於壓縮css代碼mini-css-extract-plugin
用於提取css到文件中clean-webpack-plugin
用於刪除上次構建的文件webpack-merge
合併webpack
配置copy-webpack-plugin
用戶拷貝靜態資源
5.3 開發環境配置
- build/webpack.dev.js
// build/webpack.dev.js const merge = require('webpack-merge') const webpackConfig = require('./webpack.config') const webpack = require('webpack') module.exports = merge(webpackConfig, { mode: 'development', devtool: 'cheap-module-eval-source-map', module: { rules: [ { test: /\.(scss|sass)$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader', options: { importLoaders: 2 } }, { loader: 'sass-loader', options: { implementation: require('dart-sass') } }, { loader: 'postcss-loader' } ] }, ] }, plugins: [ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('development') } }), ] })
- webpack.config.js
// build/webpack.config.js const path = require('path') const webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin') const VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { entry: { // 配置入口文件 main: path.resolve(__dirname, '../src/main.js') }, output: { // 配置打包文件輸出的目錄 path: path.resolve(__dirname, '../dist'), // 生成的 js 文件名稱 filename: 'js/[name].[hash:8].js', // 生成的 chunk 名稱 chunkFilename: 'js/[name].[hash:8].js', // 資源引用的路徑 publicPath: '/' }, devServer: { hot: true, port: 3000, contentBase: './dist' }, resolve: { alias: { vue$: 'vue/dist/vue.runtime.esm.js' }, extensions: [ '.js', '.vue' ] }, module: { rules: [ { test: /\.vue$/, use: [ { loader: 'cache-loader' }, { loader: 'vue-loader', options: { compilerOptions: { preserveWhitespace: false }, } } ] }, { test: /\.jsx?$/, loader: 'babel-loader' }, { test: /\.(jpe?g|png|gif)$/, use: [ { loader: 'url-loader', options: { limit: 4096, fallback: { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]' } } } } ] }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, use: [ { loader: 'url-loader', options: { limit: 4096, fallback: { loader: 'file-loader', options: { name: 'media/[name].[hash:8].[ext]' } } } } ] }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, use: [ { loader: 'url-loader', options: { limit: 4096, fallback: { loader: 'file-loader', options: { name: 'fonts/[name].[hash:8].[ext]' } } } } ] }, ] }, plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ template: path.resolve(__dirname, '../public/index.html') }), new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin(), ] }
5.4 生產環境配置
const path = require('path') const merge = require('webpack-merge') const webpack = require('webpack') const webpackConfig = require('./webpack.config') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const OptimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin'); /* clean-webpack-plugin 3.0 以上的版本需要使用對象結構 */ // const CleanWebpackPlugin = require('clean-webpack-plugin') const { CleanWebpackPlugin } = require('clean-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = merge(webpackConfig, { mode: 'production', devtool: '#source-map', optimization: { splitChunks: { cacheGroups: { vendors: { name: 'chunk-vendors', test: /[\\\/]node_modules[\\\/]/, priority: -10, chunks: 'initial' }, common: { name: 'chunk-common', minChunks: 2, priority: -20, chunks: 'initial', reuseExistingChunk: true } } } }, module: { rules: [ { test: /\.(scss|sass)$/, use: [ { loader: MiniCssExtractPlugin.loader }, { loader: 'css-loader', options: { importLoaders: 2 } }, { loader: 'sass-loader', options: { implementation: require('dart-sass') } }, { loader: 'postcss-loader' } ] }, ] }, plugins: [ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: 'production' } }), new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css', chunkFilename: 'css/[name].[contenthash:8].css' }), new OptimizeCssnanoPlugin({ sourceMap: true, cssnanoOptions: { preset: [ 'default', { mergeLonghand: false, cssDeclarationSorter: false } ] } }), new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../public'), to: path.resolve(__dirname, '../dist') } ]), new CleanWebpackPlugin() ] })
5.5 修改package.json
"scripts": { "serve": "webpack-dev-server --config ./build/webpack.dev.js", "build": "webpack --config ./build/webpack.prod.js" },
6 打包分析
有的時候,我們需要看一下webpack打包完成後,到底打包了什麼東西,
這時候就需要用到這個模塊分析工具了 webpack-bundle-analyzer
- 安裝依賴
npm install --save-dev webpack-bundle-analyzer
- 修改
webpack-prod.js
配置,在plugins
屬性中新增一個插件
在開發環境中,我們是沒必要進行模塊打包分析的,所以我們將插件配置在了生產環境的配置項中
- 運行打包命令
npm run build
執行成功後會自動打開這個頁面
7. 集成 VueRouter
,Vuex
- 首先是安裝相關依賴
npm install vue-router vuex --save
7.1 集成 Vue-Router
- 新增視圖組件 在
src
目錄下新增兩個視圖組件src/views/Home.vue
和src/views/About.vue
// src/views/Home.vue <template> <div class="Home"> <h2>Home</h2> </div> </template> <script> export default { name: 'Home', data() { return {}; } }; </script> <style lang="scss" scoped> </style>
About.vue
內容跟 Home.vue
差不多,將裡面的 Home
換成 About
就OK了
- 新增路由配置文件
在 src
目錄下新增一個 router/index.js
文件
// src/router/index.js import Vue from 'vue' import VueRouter from "vue-router"; import Home from '../views/Home'; import About from '../views/About'; Vue.use(VueRouter) export default new VueRouter({ mode: 'hash', routes: [ { path: '/Home', component: Home }, { path: '/About', component: About }, { path: '*', redirect: '/Home' } ] })
- 修改
main.js
文件
// main.js import Vue from 'vue' import App from './App.vue' import router from './router' new Vue({ router, render: h => h(App) }).$mount('#app')
- 修改
App.vue
組件
// App.vue // 在 template 中添加 // src/App.vue <template> <div class="App"> Hello World </div> <div> // router-link 組件 用來導航到哪個路由 <router-link to="/Home">go Home</router-link> <router-link to="/About">go About</router-link> </div> <div> // 用於展示匹配到的路由視圖組件 <router-view></router-view> </div> </template> <script> export default { name: 'App', data() { return {}; } }; </script> <style lang="scss" scoped> .App { color: skyblue; } </style>
運行 npm run serve
命令,如沒配置錯誤,是可以看到點擊不同的路由,會切換到不同的路由視圖
7.2 配置路由懶載入
在沒配置路由懶載入的情況下,我們的路由組件在打包的時候,都會打包到同一個js
文件去,當我們的視圖組件越來越多的時候,就會導致這個 js
文件越來越大。然後就會導致請求這個文件的時間變長,最終影響用戶體驗
- 安裝依賴
npm install @babel/plugin-syntax-dynamic-import --save-dev
- 修改
babel.config.js
module.exports = { presets: [ [ "@babel/preset-env", { useBuiltIns: "usage" } ] ], plugins: [ // 添加這個 '@babel/plugin-syntax-dynamic-import' ] }
- 修改
router/index.js
路由配置文件
import Vue from 'vue' import VueRouter from "vue-router"; Vue.use(VueRouter) export default new VueRouter({ mode: 'hash', routes: [ { path: '/Home', component: () => import(/* webpackChunkName: "Home" */ '../views/Home.vue') // component: Home }, { path: '/About', component: () => import(/* webpackChunkName: "About" */ '../views/About.vue') // component: About }, { path: '*', redirect: '/Home' } ] })
- 運行命令
npm run build
查看是否生成了Home...js
文件 和About...js
文件
7.3 集成 Vuex
- 在
src
目錄下新建一個store/index.js
文件
// store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const state = { counter: 0 } const actions = { add: ({commit}) => { return commit('add') } } const mutations = { add: (state) => { state.counter++ } } const getters = { getCounter (state) { return state.counter } } export default new Vuex.Store({ state, actions, mutations, getters })
- 修改
main.js
文件 導入vuex
// main.js import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' // ++ new Vue({ router, store, // ++ render: h => h(App) }).$mount('#app')
- 修改
App.vue
,查看 vuex 配置效果
// App.vue <template> <div class="App"> <div> <router-link to="/Home">go Home</router-link> <router-link to="/About">go About</router-link> </div> <div> <p>{{getCounter}}</p> <button @click="add">add</button> </div> <div> <router-view></router-view> </div> </div> </template> <script> import { mapActions, mapGetters } from 'vuex' export default { name: 'App', data() { return {}; }, computed: { ...mapGetters(['getCounter']) }, methods: { ...mapActions(['add']) } }; </script> <style lang="scss" scoped> .App { text-align: center; color: skyblue; font-size: 28px; } </style>
- 運行命令
npm run serve
當點擊按鈕的時候,可以看到我們的getCounter
一直在增加
8 總結
到目前為止,我們已經成功的自己搭建了一個 vue
開發環境,不過還是有一些功能欠缺的,有興趣的小伙伴可以交流交流。在搭建過程中,還是會踩很多坑的。
如果還不熟悉 webpack 的話,建議自己搭建一次。可以讓自己能深入的理解 vue-cli
替我們做了什麼