vue-cli構(gòu)建項(xiàng)目之webpack配置理解

概況

  • 接觸了vue有一年多,基本上已經(jīng)非常了解其中的用法,但是很多時(shí)候用歸用,涉及到其中的配置以及一些原理知識(shí),心虛得很。所以導(dǎo)致很多東西只看到表面,并不知其中,關(guān)于在vue項(xiàng)目當(dāng)中如何理解webpack配置。
  • webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler),也就是說(shuō),webpack能夠使我們的項(xiàng)目代碼模塊化,通過(guò)配置管理項(xiàng)目中的依賴包以及插件等,給我們提供打包壓縮文件等技術(shù)。今天的項(xiàng)目是基于webpack 3.6.0 + vue 2.5.2 來(lái)理解其中的配置。

一、目錄結(jié)構(gòu)

構(gòu)建項(xiàng)目目錄結(jié)構(gòu)如下,不知道怎么構(gòu)建的可以網(wǎng)上查找一下,不再贅述。


image.png

其中

build文件夾下面有:build.js、check-versions.js、utils.js、vue-loader.conf.js、webpack.base.conf.js、webpack.dev.conf.js、webpack.prod.conf.js

config文件夾下面有有:dev.env.js、index.js、prod.env.js

這些文件,都是關(guān)于webpack配置的文件。

二、首先理解package.json

{
    "name": "openlayer",      // 模塊名稱
    "version": "1.0.0",       // 版本
    "description": "A Vue.js project",    // 項(xiàng)目描述
    "author": "zhufengli",    // 作者
    "private": true,      // 私有
    "scripts": {          // 指定執(zhí)行命令
        "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",     // 執(zhí)行npm run dev或者npm start的時(shí)候就是執(zhí)行的build文件下面的webpack.dev.conf.js
        "start": "npm run dev",     // 啟動(dòng)項(xiàng)目命令
        "build": "node build/build.js"  // build命令
    },
    "dependencies": {     // 配置依賴
        "vue": "^2.5.2",
        "vue-router": "^3.0.1",
        "vuex": "^3.5.1"
    },
        "devDependencies": {      // dev開(kāi)發(fā)環(huán)境配置依賴
        "autoprefixer": "^7.1.2",
        "babel-core": "^6.22.1",
        "babel-helper-vue-jsx-merge-props": "^2.0.3",
    },
    "engines": {  // 指定node和npm版本
        "node": ">= 6.0.0",
        "npm": ">= 3.0.0"
    },
    "browserslist": [ // 支持瀏覽器配置
        "> 1%",
        "last 2 versions",
        "not ie <= 8"
    ]
}

從package的執(zhí)行命令配置中可以知道執(zhí)行的是build文件下的webpack.dev.conf.js

三、build/webpack.dev.conf.js

'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')  // 合并文件作用
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')    // 復(fù)制插件
const HtmlWebpackPlugin = require('html-webpack-plugin')    // 配置生成的文件
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')   // 友好錯(cuò)誤提示插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 清除文件插件
const portfinder = require('portfinder')    // 自動(dòng)獲取端口

const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)

// 合并了build下面的webpack.base.conf.js
const devWebpackConfig = merge(baseWebpackConfig, {
    module: {
        // 資源管理配置,處理各種文件類型
        rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
    },
    // 主要是定位錯(cuò)誤(用于開(kāi)發(fā))
    devtool: config.dev.devtool,  // 'cheap-module-eval-source-map'

    // 這些devServer選項(xiàng)應(yīng)在config/index.js中自定義
    devServer: {
        clientLogLevel: 'warning',
        historyApiFallback: {
            rewrites: [
                { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
            ],
        },
        hot: true, 
        contentBase: false, 
        compress: true,     
        host: HOST || config.dev.host,      // ip地址
        port: PORT || config.dev.port,      // 端口
        open: config.dev.autoOpenBrowser,   // 運(yùn)行npm run dev成功之后自動(dòng)打開(kāi)瀏覽器窗口
        overlay: config.dev.errorOverlay
          ? { warnings: false, errors: true }
          : false,
        publicPath: config.dev.assetsPublicPath,
        proxy: config.dev.proxyTable,       // 代理
        quiet: true, 
        watchOptions: {
            poll: config.dev.poll,
        }
    },
    // 插件配置
    plugins: [
        new webpack.DefinePlugin({
            'process.env': require('../config/dev.env'),
            'process.env.NODE_ENV': 'pro'
        }),
        new webpack.HotModuleReplacementPlugin(),    // 熱更新
        new webpack.NamedModulesPlugin(), 
        new webpack.NoEmitOnErrorsPlugin(),
        
        // 這個(gè)打包本來(lái)配了兩個(gè)文件,發(fā)現(xiàn)沒(méi)效果
        new HtmlWebpackPlugin({
            title:"openlayer",//它的title為app,在index.html的title中間中加入<%= %>
            filename: 'index.html',   // 輸出的文件名
            template: 'index.html',   // 模板文件
            inject: true,
            minify: {
                removeComments: true,               // 移除HTML中的注釋
                removeScriptTypeAttributes: true,   // 刪除script的類型屬性,在h5下面script的type默認(rèn)值:text/javascript 默認(rèn)值false
                removeAttributeQuotes: true,        // 是否移除屬性的引號(hào) 默認(rèn)false
                useShortDoctype: true,              // 使用短的文檔類型,默認(rèn)false
                decodeEntities: true,
                collapseWhitespace: true,           // 刪除空白符與換行符
                minifyCSS: true                     // 是否壓縮html里的css(使用clean-css進(jìn)行的壓縮) 默認(rèn)值false
            },
            hash:true,
            chunks:['app']
        }),
        new HtmlWebpackPlugin({
            title:"test",
            filename: 'test.html',
            template: 'test.html',
            hash:true,
            inject:true,
            chunks:['test']
        }),
        new CleanWebpackPlugin({
            root: path.resolve(__dirname, '..'),
            dry: false    // 啟用刪除文件
        }),
        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 {
          
          process.env.PORT = port
          // 將端口添加到devServer配置
          devWebpackConfig.devServer.port = port
    
          // 添加 FriendlyErrorsPlugin
          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)
        }
    })
})

通過(guò)webpack.dev.conf.js merge合并文件 webpack.base.conf.js 可以知道基礎(chǔ)的配置文件都在這里

四、build/webpack.base.conf.js

'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: './src/main.js',  // 入口文件
        test: './src/test.js'
    },
    // 出口文件配置
    output: {
        path: config.build.assetsRoot,  // 輸出文件路徑
        filename: '[name].js',          // 輸出文件名
        publicPath: process.env.NODE_ENV === 'pro'    
      ? config.build.assetsPublicPath
      : config.build.assetsPublicPath   // 公共存放路徑
        // 為什么用一樣的路勁config.build.assetsPublicPath,下面詳解
    },
    resolve: {
        // 擴(kuò)展文件后綴,這樣在引入文件的時(shí)候可以忽略后綴名
        // 如 import 'js/index'     js/index.js
        extensions: ['.js', '.vue', '.json'],
        
        // 配置別名
        alias: {   
            'vue$': 'vue/dist/vue.esm.js',
            '@': resolve('src'),
            'src': resolve('src'),
        }
    },
  
    // 文件處理
    module: {
        rules: [
            // vue文件語(yǔ)法處理
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: vueLoaderConfig    // (下面詳解)
            },
            // 語(yǔ)法處理,會(huì)處理成瀏覽器能夠識(shí)別的ES5語(yǔ)法
            {
                test: /\.js$/,
                loader: 'babel-loader',
                include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
            },
            // 圖片處理
            {
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                loader: 'url-loader',
                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: {
        //防止Webpack注入無(wú)用的setImmediate polyfill。
        setImmediate: false,
        // 阻止webpack將模擬注入到Node本機(jī)模塊
        dgram: 'empty',
        fs: 'empty',
        net: 'empty',
        tls: 'empty',
        child_process: 'empty'
    }
}

五、build/webpack.prod.conf.js

生產(chǎn)環(huán)境配置,暫不深入理解,想要了解的小伙伴可以看下這篇文章 webpack.prod.conf.js文件詳解

六、build/vue-loader.conf.js

這個(gè)文件主要是處理vue文件,主要是sass、less用的比較多,這里需要更加正確理解。

'use strict'
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規(guī)則處理,包括sass、less、postcss等
    loaders: utils.cssLoaders({
        sourceMap: sourceMapEnabled,    // 調(diào)式作用
        extract: isProduction
    }),
    cssSourceMap: sourceMapEnabled,
    cacheBusting: config.dev.cacheBusting,
    
    // 可以將某些屬性轉(zhuǎn)成require調(diào)用
    transformToRequire: {
        video: ['src', 'poster'],
        source: 'src',
        img: 'src',
        image: 'xlink:href'
    }
}

七、build/utils.js

utils 工具文件,主要作用分為

  1. 配置導(dǎo)出路徑
  2. 處理各類loader相關(guān)配置
  3. 跨平臺(tái)通知系統(tǒng)
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')  // 用來(lái)將文本從bundle中提取到一個(gè)單獨(dú)的文件中
const packageConfig = require('../package.json')

// 導(dǎo)出路徑
exports.assetsPath = function (_path) {
    const assetsSubDirectory = process.env.NODE_ENV === 'production' 
    ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory
    
    // 返回一個(gè)完整路徑的相對(duì)根路徑
    return path.posix.join(assetsSubDirectory, _path) 
}

// 導(dǎo)出cssLoaders相關(guān)配置
exports.cssLoaders = function (options) {
    options = options || {}

    const cssLoader = {
        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, {
                    sourceMap: options.sourceMap
                })
            })
        }
    
        // 指定該選項(xiàng)時(shí)提取CSS(生產(chǎn)構(gòu)建)
        if (options.extract) {
            return ExtractTextPlugin.extract({
                use: loaders,
                fallback: 'vue-style-loader'
            })
        } else {
            return ['vue-style-loader'].concat(loaders)
        }
    }
    
    // https://vue-loader.vuejs.org/en/configurations/extract-css.html
    return {
        css: generateLoaders(),             // 對(duì)應(yīng) vue-style-loader 和 css-loader
        postcss: generateLoaders(),         // 對(duì)應(yīng) vue-style-loader 和 css-loader
        less: generateLoaders('less'),      // 對(duì)應(yīng) vue-style-loader 和 less-loader
        sass: generateLoaders('sass', { indentedSyntax: true }),    // 對(duì)應(yīng) vue-style-loader 和 sass-loader
        scss: generateLoaders('sass'),      // 對(duì)應(yīng) vue-style-loader 和 sass-loader
        stylus: generateLoaders('stylus'),  // 對(duì)應(yīng) vue-style-loader 和 stylus-loader
        styl: generateLoaders('stylus')     // 對(duì)應(yīng) vue-style-loader 和 styl-loader
    }
}

// 為獨(dú)立樣式文件生成加載程序(.vue之外)
exports.styleLoaders = function (options) {
    const output = []
      
    // 生成的各種css文件的loader對(duì)象
    const loaders = exports.cssLoaders(options)
    
    // 把每個(gè)文件的loader提取出來(lái),push到output數(shù)組中
    for (const extension in loaders) {
        const loader = loaders[extension]
        output.push({
            test: new RegExp('\\.' + extension + '$'),
            use: loader
        })
    }
    
    return output
}

// 發(fā)送跨平臺(tái)通知系統(tǒng)
exports.createNotifierCallback = () => {
    const notifier = require('node-notifier')
    
    // 出現(xiàn)error時(shí)觸發(fā)
    return (severity, errors) => {
        if (severity !== 'error') return
    
        const error = errors[0]
        const filename = error.file && error.file.split('!').pop()
    
        notifier.notify({
            title: packageConfig.name,  // 標(biāo)題
            message: severity + ': ' + error.name,  // 內(nèi)容
            subtitle: filename || '',               // 短標(biāo)題
            icon: path.join(__dirname, 'logo.png')  // 圖標(biāo)
        })
    }
}

關(guān)于sass和less,在我們初始化項(xiàng)目的時(shí)候并沒(méi)有默認(rèn)安裝,而在項(xiàng)目中,基本上都有用到,比如寫(xiě)一個(gè).sass文件或者寫(xiě)一段代碼

<style lang="sass" scoped>
    
</style>

可能,就會(huì)報(bào)錯(cuò)如下


image.png

這時(shí)候你就需要安裝一下node-sass、sass-loader和scss

npm install node-sass
npm install sass-loader
npm install scss

安裝完之后你以為萬(wàn)事大吉了,沒(méi)想到只是從一個(gè)坑掉進(jìn)另一個(gè)坑


image.png

這意思是路徑錯(cuò)誤?查找了一番之后原來(lái)是版本過(guò)高,package.json里面可以查看版本10.0.1,卸載,重新裝一個(gè)低版本的7.3.1,這次真的可以了


image.png
npm uninstall sass-loader
npm install sass-loader@7.3.1

七、build/build.js和build/check-versions.js

暫不深入理解,想要了解的小伙伴可以看下參考文章 vue-cli腳手架中webpack配置基礎(chǔ)文件詳解

講完build文件夾,還有一個(gè)config文件夾,這個(gè)文件夾主是定義一些變量exports出去給build文件夾下面的文件使用

八、config/dev.env.js

開(kāi)發(fā)環(huán)境配置

'use strict'
module.exports = {
    NODE_ENV: '"dev"'
}

九、config/prod.env.js

生產(chǎn)環(huán)境配置

'use strict'
module.exports = {
    NODE_ENV: '"pro"'
}

八、config/index.js

定義一些開(kāi)發(fā)/打包需要的變量

'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')

module.exports = {
    dev: {
        assetsSubDirectory: 'static',
        assetsPublicPath: '/',  // 公共路勁
        proxyTable: {},

        // 各種開(kāi)發(fā)服務(wù)器設(shè)置
        host: 'localhost',  // 可以被process.env.HOST覆蓋
        port: 8080,     //端口, 可以被process.env.PORT覆蓋,如果正在使用端口,則將確定一個(gè)空閑端口
        autoOpenBrowser: false,     // 運(yùn)行自動(dòng)打開(kāi)瀏覽器
        errorOverlay: true,         // 錯(cuò)誤提示
        notifyOnErrors: true,       // 跨平臺(tái)錯(cuò)誤提示
        poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
        
        /**
         *  Source Maps 代碼調(diào)試BUG、錯(cuò)誤等
            https://webpack.js.org/configuration/devtool/#development
         */
        devtool: 'cheap-module-eval-source-map',
        // cheap-module-eval-source-map 開(kāi)發(fā)環(huán)境(dev)推薦使用
        // cheap-module-source-map  可以定位生產(chǎn)環(huán)境的代碼報(bào)錯(cuò)
        

        //如果在devtools中調(diào)試vue文件時(shí)遇到問(wèn)題,
        //將其設(shè)置為false-可能會(huì)有所幫助
        // https://vue-loader.vuejs.org/en/options.html#cachebusting
        cacheBusting: true,     // 緩存失效
        cssSourceMap: true
    },
    
    // 打包配置
    build: {
        index: path.resolve(__dirname, '../dist/index.html'), // 編譯后生成的文件位置
        assetsRoot: path.resolve(__dirname, '../dist'), // 打包后存放代碼位置
        assetsSubDirectory: 'static',   // 靜態(tài)文件夾(js、css、images)
        assetsPublicPath: './', // 發(fā)布根目錄

        productionSourceMap: true,
        devtool: '#source-map',
        productionGzip: false,
        productionGzipExtensions: ['js', 'css'],
        bundleAnalyzerReport: process.env.npm_config_report
    }
}

十一、項(xiàng)目遇坑記

為了弄清楚一下項(xiàng)目中使用的插件,以及一些重要的屬性,進(jìn)行的一些屬性試驗(yàn)

1. 關(guān)于npm run build 打包編譯

這里有個(gè)問(wèn)題是關(guān)于輸出公共存放路徑 config.build.assetsPublicPath 在我打包完之后打開(kāi)dist/index.html,頁(yè)面是空白,然后報(bào)錯(cuò)如下


image.png

經(jīng)過(guò)一番查找之后,得到結(jié)果是在config/index.js文件下dev和build下面的 assetsPublicPath '/' 修改成 './' ,改完之后發(fā)現(xiàn)npm run dev 的時(shí)候頁(yè)面找不到了,所以為了兼容打包和運(yùn)行,打包的時(shí)候統(tǒng)一使用config.build.assetsPublicPath,而dev下面的assetsPublicPath還是改回原來(lái)的 '/'。


image.png

image.png

打包好之后的文件在根目錄下面會(huì)生成一個(gè)dist文件夾,里面的結(jié)構(gòu)如下,其中,static主要存放靜態(tài)資源(css、js、images、fonts等)


image.png
2. 關(guān)于devtool: 'cheap-module-eval-source-map'

為什么需要這個(gè)東西,主要的作用是幫助我們精準(zhǔn)定位錯(cuò)誤信息,如

image.png

我在helloWorld.vue文件調(diào)用了print文件里面的consoleLog函數(shù),函數(shù)內(nèi)容我寫(xiě)的很簡(jiǎn)單

let consoleLog = () =>{
    console.log('這是正確的打印');
    cosnole.error('這是錯(cuò)誤的打印')
}

export default {
    consoleLog
}

如果沒(méi)有devtool: '',那么提示的錯(cuò)誤信息


image.png

它雖然告訴你cosnole is not defined,但是并沒(méi)有告訴你在哪個(gè)文件哪一行

但是如果有devtool: 'cheap-module-eval-source-map',他的提示信息可以非常準(zhǔn)確定位到文件以及行位置


image.png

devtool相關(guān)知識(shí)

3.關(guān)于clean-webpack-plugin 清除文件插件

按著上面插件使用方法,直接報(bào)了一個(gè)不是構(gòu)造函數(shù)的錯(cuò)誤,一臉懵


image.png
image.png

后來(lái)去看官方文檔引入


image.png

使用的時(shí)候查找了很多文章有這類的使用方法

new CleanWebpackPlugin(['dist'],{
    root: path.resolve(__dirname, '..'),
    dry: false // 啟用刪除文件
}),

果然不行,這不是就是參數(shù)類型傳得不對(duì)嗎


image.png

換成下面這種參數(shù)吧,估計(jì)這個(gè)與版本有關(guān),我的是3.0.0

new CleanWebpackPlugin({
    root: path.resolve(__dirname, '..'),
    dry: false // 啟用刪除文件
}),

clean-webpack-plugin相關(guān)知識(shí)

結(jié)語(yǔ)

很多時(shí)候,好記性真是不如爛筆頭,寫(xiě)了一遍之后基本上知道理解其中得工作原理,以及相關(guān)一些配置,還有一些生產(chǎn)環(huán)境得配置已經(jīng)應(yīng)用還沒(méi)有真正深入理解,后面有時(shí)間有精力一定補(bǔ)上。附上webpack官方文檔,我也只看到【開(kāi)發(fā)】額。。。

參考文章:
vue-cli腳手架中webpack配置基礎(chǔ)文件詳解
vue-cli的utils.js文件詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容