vue-cli构建项目之webpack配置理解

2022-07-31  本文已影响0人  稀释1

一、目录结构

构建项目目录结构如下,不知道怎么构建的可以网上查找一下,不再赘述。

image

其中

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

这些文件,都是关于webpack配置的文件。

二、首先理解package.json

{
    "name": "openlayer",      // 模块名称
    "version": "1.0.0",       // 版本
    "description": "A Vue.js project",    // 项目描述
    "author": "zhufengli",    // 作者
    "private": true,      // 私有
    "scripts": {          // 指定执行命令
        "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",     // 执行npm run dev或者npm start的时候就是执行的build文件下面的webpack.dev.conf.js
        "start": "npm run dev",     // 启动项目命令
        "build": "node build/build.js"  // build命令
    },
    "dependencies": {     // 配置依赖
        "vue": "^2.5.2",
        "vue-router": "^3.0.1",
        "vuex": "^3.5.1"
    },
        "devDependencies": {      // dev开发环境配置依赖
        "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的执行命令配置中可以知道执行的是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')    // 复制插件
const HtmlWebpackPlugin = require('html-webpack-plugin')    // 配置生成的文件
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')   // 友好错误提示插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 清除文件插件
const portfinder = require('portfinder')    // 自动获取端口

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 })
    },
    // 主要是定位错误(用于开发)
    devtool: config.dev.devtool,  // 'cheap-module-eval-source-map'

    // 这些devServer选项应在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,   // 运行npm run dev成功之后自动打开浏览器窗口
        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(),

        // 这个打包本来配了两个文件,发现没效果
        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默认值:text/javascript 默认值false
                removeAttributeQuotes: true,        // 是否移除属性的引号 默认false
                useShortDoctype: true,              // 使用短的文档类型,默认false
                decodeEntities: true,
                collapseWhitespace: true,           // 删除空白符与换行符
                minifyCSS: true                     // 是否压缩html里的css(使用clean-css进行的压缩) 默认值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)
        }
    })
})

通过webpack.dev.conf.js merge合并文件 webpack.base.conf.js 可以知道基础的配置文件都在这里

四、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: {
        // 扩展文件后缀,这样在引入文件的时候可以忽略后缀名
        // 如 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文件语法处理
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: vueLoaderConfig    // (下面详解)
            },
            // 语法处理,会处理成浏览器能够识别的ES5语法
            {
                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注入无用的setImmediate polyfill。
        setImmediate: false,
        // 阻止webpack将模拟注入到Node本机模块
        dgram: 'empty',
        fs: 'empty',
        net: 'empty',
        tls: 'empty',
        child_process: 'empty'
    }
}

五、build/webpack.prod.conf.js

生产环境配置,暂不深入理解,想要了解的小伙伴可以看下这篇文章 webpack.prod.conf.js文件详解

六、build/vue-loader.conf.js

这个文件主要是处理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规则处理,包括sass、less、postcss等
    loaders: utils.cssLoaders({
        sourceMap: sourceMapEnabled,    // 调式作用
        extract: isProduction
    }),
    cssSourceMap: sourceMapEnabled,
    cacheBusting: config.dev.cacheBusting,

    // 可以将某些属性转成require调用
    transformToRequire: {
        video: ['src', 'poster'],
        source: 'src',
        img: 'src',
        image: 'xlink:href'
    }
}

七、build/utils.js

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

  1. 配置导出路径
  2. 处理各类loader相关配置
  3. 跨平台通知系统
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')  // 用来将文本从bundle中提取到一个单独的文件中
const packageConfig = require('../package.json')

// 导出路径
exports.assetsPath = function (_path) {
    const assetsSubDirectory = process.env.NODE_ENV === 'production' 
    ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory

    // 返回一个完整路径的相对根路径
    return path.posix.join(assetsSubDirectory, _path) 
}

// 导出cssLoaders相关配置
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
                })
            })
        }

        // 指定该选项时提取CSS(生产构建)
        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(),             // 对应 vue-style-loader 和 css-loader
        postcss: generateLoaders(),         // 对应 vue-style-loader 和 css-loader
        less: generateLoaders('less'),      // 对应 vue-style-loader 和 less-loader
        sass: generateLoaders('sass', { indentedSyntax: true }),    // 对应 vue-style-loader 和 sass-loader
        scss: generateLoaders('sass'),      // 对应 vue-style-loader 和 sass-loader
        stylus: generateLoaders('stylus'),  // 对应 vue-style-loader 和 stylus-loader
        styl: generateLoaders('stylus')     // 对应 vue-style-loader 和 styl-loader
    }
}

// 为独立样式文件生成加载程序(.vue之外)
exports.styleLoaders = function (options) {
    const output = []

    // 生成的各种css文件的loader对象
    const loaders = exports.cssLoaders(options)

    // 把每个文件的loader提取出来,push到output数组中
    for (const extension in loaders) {
        const loader = loaders[extension]
        output.push({
            test: new RegExp('\\.' + extension + '$'),
            use: loader
        })
    }

    return output
}

// 发送跨平台通知系统
exports.createNotifierCallback = () => {
    const notifier = require('node-notifier')

    // 出现error时触发
    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')  // 图标
        })
    }
}

关于sass和less,在我们初始化项目的时候并没有默认安装,而在项目中,基本上都有用到,比如写一个.sass文件或者写一段代码

<style lang="sass" scoped>

</style>

可能,就会报错如下

image

这时候你就需要安装一下node-sass、sass-loader和scss

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

安装完之后你以为万事大吉了,没想到只是从一个坑掉进另一个坑

image

这意思是路径错误?查找了一番之后原来是版本过高,package.json里面可以查看版本10.0.1,卸载,重新装一个低版本的7.3.1,这次真的可以了

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

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

暂不深入理解,想要了解的小伙伴可以看下参考文章 vue-cli脚手架中webpack配置基础文件详解

讲完build文件夹,还有一个config文件夹,这个文件夹主是定义一些变量exports出去给build文件夹下面的文件使用

八、config/dev.env.js

开发环境配置

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

九、config/prod.env.js

生产环境配置

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

八、config/index.js

定义一些开发/打包需要的变量

'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: {},

        // 各种开发服务器设置
        host: 'localhost',  // 可以被process.env.HOST覆盖
        port: 8080,     //端口, 可以被process.env.PORT覆盖,如果正在使用端口,则将确定一个空闲端口
        autoOpenBrowser: false,     // 运行自动打开浏览器
        errorOverlay: true,         // 错误提示
        notifyOnErrors: true,       // 跨平台错误提示
        poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-

        /**
         *  Source Maps 代码调试BUG、错误等
            https://webpack.js.org/configuration/devtool/#development
         */
        devtool: 'cheap-module-eval-source-map',
        // cheap-module-eval-source-map 开发环境(dev)推荐使用
        // cheap-module-source-map  可以定位生产环境的代码报错

        //如果在devtools中调试vue文件时遇到问题,
        //将其设置为false-可能会有所帮助
        // 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',   // 静态文件夹(js、css、images)
        assetsPublicPath: './', // 发布根目录

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

十一、项目遇坑记

为了弄清楚一下项目中使用的插件,以及一些重要的属性,进行的一些属性试验

1. 关于npm run build 打包编译

这里有个问题是关于输出公共存放路径 config.build.assetsPublicPath 在我打包完之后打开dist/index.html,页面是空白,然后报错如下

image

经过一番查找之后,得到结果是在config/index.js文件下dev和build下面的 assetsPublicPath '/' 修改成 './' ,改完之后发现npm run dev 的时候页面找不到了,所以为了兼容打包和运行,打包的时候统一使用config.build.assetsPublicPath,而dev下面的assetsPublicPath还是改回原来的 '/'。

image image

打包好之后的文件在根目录下面会生成一个dist文件夹,里面的结构如下,其中,static主要存放静态资源(css、js、images、fonts等)

image
2. 关于devtool: 'cheap-module-eval-source-map'

为什么需要这个东西,主要的作用是帮助我们精准定位错误信息,如

image

我在helloWorld.vue文件调用了print文件里面的consoleLog函数,函数内容我写的很简单

let consoleLog = () =>{
    console.log('这是正确的打印');
    cosnole.error('这是错误的打印')
}

export default {
    consoleLog
}

如果没有devtool: '',那么提示的错误信息

image

它虽然告诉你cosnole is not defined,但是并没有告诉你在哪个文件哪一行

但是如果有devtool: 'cheap-module-eval-source-map',他的提示信息可以非常准确定位到文件以及行位置

image

devtool相关知识

3.关于clean-webpack-plugin 清除文件插件

按着上面插件使用方法,直接报了一个不是构造函数的错误,一脸懵

image image

后来去看官方文档引入

image

使用的时候查找了很多文章有这类的使用方法

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

果然不行,这不是就是参数类型传得不对吗

image

换成下面这种参数吧,估计这个与版本有关,我的是3.0.0

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

链接:https://www.jianshu.com/p/051fcd267316

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
参考:https://segmentfault.com/a/1190000014804826

上一篇下一篇

猜你喜欢

热点阅读