前段時間,看到羣裏一些小夥伴面試的時候被面試官問到這類題目。平時你們開發vue項目的時候,相信大部分人都是使用 vue-cli
腳手架生成的項目架構,而後
npm run install
安裝依賴,npm run serve
啓動項目而後就開始寫業務代碼了。css
可是對項目裏的webpack
封裝和配置瞭解的不清楚,容易致使出問題不知如何解決,或者不會經過webpack
去擴展新功能。html
該篇文章主要是想告訴小夥伴們,如何一步一步的經過 webpack4
來搭建本身的vue
開發環境vue
首先咱們要知道 vue-cli
生成的項目,幫咱們配置好了哪些功能?node
ES6
代碼轉換成ES5
代碼scss/sass/less/stylus
轉css
.vue
文件轉換成js
文件jpg
、png
,font
等資源文件webpack
基本環境該篇文章並不會細講 webpack
是什麼東西,若是還不是很清楚的話,能夠先去看看 webpack官網webpack
簡單的說,webpack
是一個模塊打包機,能夠分析你的項目依賴的模塊以及一些瀏覽器不能直接運行的語言jsx
、vue
等轉換成 js
、css
文件等,供瀏覽器使用。css3
在命令行中執行 npm init
而後一路回車就好了,主要是生成一些項目基本信息。最後會生成一個 package.json
文件git
npm init
webpack
webpack
是否安裝成功了新建一個src
文件夾,而後再建一個main.js
文件es6
// src/main.js console.log('hello webpack')
而後在 package.json 下面加一個腳本命令github
而後運行該命令web
npm run serve
若是在 dist 目錄下生成了一個main.js
文件,則表示webpack
工做正常
build
文件夾,用來存放 webpack
配置相關的文件build
文件夾下新建一個webpack.config.js
,配置webpack
的基本配置webpack.config.js
配置package.json
文件,將以前添加的 serve
修改成"serve": "webpack ./src/main.js --config ./build/webpack.config.js"
ES6/7/8
轉 ES5
代碼npm install babel-loader @babel/core @babel/preset-env
webpack.config.js
配置babel.config.js
文件npm run serve
命令,能夠看到 ES6代碼被轉成了ES5代碼了ES6/7/8 Api
轉es5
babel-loader
只會將 ES6/7/8語法轉換爲ES5語法,可是對新api並不會轉換。
咱們能夠經過 babel-polyfill 對一些不支持新語法的客戶端提供新語法的實現
npm install @babel/polyfill
webpack.config.js
配置在 entry
中添加 @babel-polyfill
polyfill
2.1.2 和 2.1.1 只須要配置一個就行
修改時間 2019-05-0五、 來自評論區 兮漫天 的提醒
npm install core-js@2 @babel/runtime-corejs2 -S
配置了按需引入 polyfill
後,用到es6
以上的函數,babel
會自動導入相關的polyfill
,這樣能大大減小 打包編譯後的體積
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
配置npm install postcss-loader autoprefixer -D
webpack.config.js
配置postcss.config.js
html-webpack-plugin
來建立html頁面使用 html-webpack-plugin
來建立html頁面,並自動引入打包生成的js
文件
npm install html-webpack-plugin -D
<!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
配置經過代碼的熱更新功能,咱們能夠實現不刷新頁面的狀況下,更新咱們的頁面
npm install webpack-dev-server -D
webpack.config.js
配置經過配置 devServer
和 HotModuleReplacementPlugin
插件來實現熱更新
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: [ // ... ] }
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 <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
經過 webpack
提供的DefinePlugin
插件,能夠很方便的定義環境變量
plugins: [ new webpack.DefinePlugin({ 'process.env': { VUE_APP_BASE_URL: JSON.stringify('http://localhost:3000') } }), ]
新建兩個文件
webpack.dev.js
開發環境使用webpack.prod.js
生產環境使用webpack.config.js
公用配置
開發環境與生產環境的不一樣
...
...
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
用戶拷貝靜態資源// 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') } }), ] })
// 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(), ] }
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'); 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() ] })
"scripts": { "serve": "webpack-dev-server --config ./build/webpack.dev.js", "build": "webpack --config ./build/webpack.prod.js" },
有的時候,咱們須要看一下webpack打包完成後,到底打包了什麼東西,
這時候就須要用到這個模塊分析工具了 webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
webpack-prod.js
配置,在 plugins
屬性中新增一個插件在開發環境中,咱們是不必進行模塊打包分析的,因此咱們將插件配置在了生產環境的配置項中
npm run build
執行成功後會自動打開這個頁面
VueRouter
,Vuex
npm install vue-router vuex --save
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
命令,如沒配置錯誤,是能夠看到點擊不一樣的路由,會切換到不一樣的路由視圖
在沒配置路由懶加載的狀況下,咱們的路由組件在打包的時候,都會打包到同一個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
文件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
一直在增長
到目前爲止,咱們已經成功的本身搭建了一個 vue
開發環境,不過仍是有一些功能欠缺的,有興趣的小夥伴能夠交流交流。在搭建過程當中,仍是會踩不少坑的。
若是還不熟悉 webpack 的話,建議本身搭建一次。可讓本身能深刻的理解 vue-cli
替咱們作了什麼
歡迎關注公衆號「碼上開發」,天天分享最新技術資訊