Webpack 4 以及打包优化

在上篇文章基础上https://blog.csdn.net/weixin_43554698/article/details/89914721
优化:

  • 1.文件压缩(js、css、图片)
  • 2.打包速度优化.使用happypack和thread-loader加速构建(由于HappyPack 对file-loader、url-loader 支持的不友好,所以不建议对该loader使用)
  • 3.提取公共代码
  • 4.使用第三方类库(如果想要缩小打包体积Tree Shaking需要使用ES6模块和UglifyJsPlugin插件,以及配置optimization选项,设置usedExports和sideEffects为true)

修改上次目录结构
在这里插入图片描述

  • 需要下载的依赖package.js
{
    "name": "webpack4-vue",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "dev": "cross-env BUILD_MODE=dev webpack-dev-server ",
        "build": "cross-env BUILD_MODE=prod webpack "
    },
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "@babel/core": "^7.0.0",
        "@babel/plugin-proposal-class-properties": "^7.2.3",
        "@babel/plugin-proposal-decorators": "^7.2.3",
        "@babel/plugin-syntax-dynamic-import": "^7.2.0",
        "@babel/plugin-transform-runtime": "^7.0.0",
        "@babel/polyfill": "^7.2.5",
        "@babel/preset-env": "^7.0.0",
        "@babel/runtime": "^7.0.0",
        "babel-loader": "^8.0.5",
        "clean-webpack-plugin": "^0.1.19",
        "copy-webpack-plugin": "^5.0.3",
        "cross-env": "^5.2.0",
        "css-loader": "^0.28.11",
        "glob": "^7.0.3",
        "happypack": "^5.0.1",
        "html-webpack-plugin": "^3.2.0",
        "mini-css-extract-plugin": "^0.6.0",
        "optimize-css-assets-webpack-plugin": "^5.0.1",
        "postcss-loader": "^2.1.5",
        "sass-loader": "^7.0.3",
        "sass-resources-loader": "^1.3.3",
        "style-loader": "^0.21.0",
        "uglifyjs-webpack-plugin": "^1.2.7",
        "url-loader": "^1.1.2",
        "vue-loader": "^15.7.0",
        "vue-style-loader": "^4.1.2",
        "vue-template-compiler": "^2.6.10",
        "webpack": "^4.30.0",
        "webpack-cli": "^3.3.2",
        "webpack-dev-server": "^3.3.1",
        "webpack-merge": "^4.2.1"
    },
    "dependencies": {
        "vue": "^2.6.10"
    }
}

  • webpack.config.js配置
// package.json中通过 --BUILD_MODE 指定当前执行的配置文件
const env = process.env.BUILD_MODE.trim();
module.exports = require(`./build/webpack.${env}.js`);
  • build/config.js中多页的配置信息:

这个文件是为了配合多入口文件,这里只有一个所以就写一个就可以
在这里插入图片描述

  • build/webpack.base.js基础配置文件
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack')
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const config = require('./config'); // 多页面的配置项
let HTMLPlugins = [];
let Entries = {};

config.HTMLDirs.forEach(item => {
    let filename = `${item.page}.html`;
    const htmlPlugin = new HTMLWebpackPlugin({
        title: item.title, // 生成的html页面的标题
        filename: filename, // 生成到dist目录下的html文件名称,支持多级目录(eg: `${item.page}/index.html`)
        template: path.resolve(__dirname, `../src/template/${item.page}.html`), // 模板文件,不同入口可以根据需要设置不同模板
        chunks: [item.page, 'vendor'], // html文件中需要要引入的js模块,这里的 vendor 是webpack默认配置下抽离的公共模块的名称
    });
    HTMLPlugins.push(htmlPlugin);
    Entries[item.page] = path.resolve(__dirname, `../src/mainJs/${item.page}.js`); // 根据配置设置入口js文件
});

const env = process.env.BUILD_MODE.trim();
let ASSET_PATH = '/'; // dev 环境
if (env === 'prod') ASSET_PATH = './'; // build 时设置成实际使用的静态服务地址

module.exports = {
    entry: Entries,
    output: {
        publicPath: ASSET_PATH,
        filename: 'js/[name].[hash:8].js',
        path: path.resolve(__dirname, '../dist'),
    },
    module: {
        rules: [{
                test: /\.vue$/, // 处理vue模块
                use: 'vue-loader',
            },
            {
                test: /\.js$/, //处理es6语法
                exclude: /node_modules/,
                use: ['babel-loader'],
            }
        ]
    },
    resolve: { // 设置模块如何被解析
        alias: {
            '@': path.resolve(__dirname, '../src')
        },
        extensions: ['*', '.css', '.js', '.vue']
    },
    plugins: [
        new VueLoaderPlugin(),
        new CopyWebpackPlugin([{
                from: path.resolve(__dirname, '../src/statick'),
                to: path.resolve(__dirname, '../dist'),
                ignore: ['*.html']
            },
            // {
            //     from: path.resolve(__dirname, '../src/scripts/lib'),
            //     to: path.resolve(__dirname, '../dist')
            // }
        ]),
        ...HTMLPlugins, // 利用 HTMLWebpackPlugin 插件合成最终页面
        new webpack.DefinePlugin({
            'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH) // 利用 process.env.ASSET_PATH 保证模板文件中引用正确的静态资源地址
        })
    ]
};

vue-loader要配合 VueLoaderPlugin 插件一起使用。 babel-loader 要配合 .babelrc 使用。这里配置“stage-2”以使用es7里的高级语法,实测如果不配置就无法处理 对象扩展符、async和await 等新语法特性。

  • .babelrc配置:
{
   // targets, useBuiltIns 等选项用于编译出兼容目标环境的代码
  // 其中 useBuiltIns 如果设为 "usage"
  // Babel 会根据实际代码中使用的 ES6/ES7 代码,以及与你指定的 targets,按需引入对应的 polyfill
  // 而无需在代码中直接引入 import '@babel/polyfill',避免输出的包过大,同时又可以放心使用各种新语法特性。
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false,
        "targets": {
          "browsers": [
            "> 1%",
            "last 2 versions",
            "ie >= 11"
          ]
        },
        "useBuiltIns": "usage" // 按需引入 polyfill
      }
    ]
  ],
  "plugins": [
    "@babel/plugin-transform-runtime",
    "@babel/plugin-syntax-dynamic-import",
    ["@babel/plugin-proposal-class-properties", { "loose": false }],
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
  ]
}

关于 .babelrc 相关的配置可参考: 官方文档; babel配置-各阶段的stage的区别

  • postcss.config.js配置
module.exports = {
  plugins: {
    "autoprefixer": {
      browsers: ["last 5 version", "Android >= 4.0"],
      //是否美化属性值 默认:true
      cascade: true,
      //是否去掉不必要的前缀 默认:true
      remove: true
    }
  }
}
  • build/webpack.dev.js开发配置文件
const path = require('path');
const webpackBase = require('./webpack.base');
const webpackMerge = require('webpack-merge');
const config = require('./config');

module.exports = webpackMerge(webpackBase, {
    mode: 'development',
    module: {
        rules: [{
                test: /\.css$/,
                // 开发模式下使用 vue-style-loader,以便使用热重载
                use: [
                    'vue-style-loader',
                    'css-loader',
                    'postcss-loader',
                ]
            },
            {
                test: /\.scss$/,
                exclude: /node_modules/,
                use: [
                    'vue-style-loader',
                    'css-loader',
                    'sass-loader',
                    'postcss-loader',
                    {
                        loader: 'sass-resources-loader',
                        options: {
                            resources: path.resolve(__dirname, '../src/styles/lib/main.scss'),
                        }
                    }
                ]
            },
            // {
            //     test: /\.(js|vue)$/,
            //     enforce: 'pre', // 强制先进行 ESLint 检查
            //     exclude: /node_modules|lib/,
            //     loader: 'eslint-loader',
            //     options: {
            //         // 启用自动修复
            //         fix: true,
            //         // 启用警告信息
            //         emitWarning: true,
            //     }
            // },
            {
                test: /\.(png|svg|jpg|gif)$/, // 处理图片
                use: {
                    loader: 'url-loader', // 解决打包css文件中图片路径无法解析的问题
                    options: {
                        // 打包生成图片的名字
                        name: '[name].[hash:8].[ext]',
                        // 图片的生成路径
                        outputPath: config.imgOutputPath,
                    }
                }
            },
            // {
            //     test: /\.(woff|woff2|eot|ttf|otf)$/, // 处理字体
            //     use: {
            //         loader: 'url-loader',
            //         options: {
            //             outputPath: config.fontOutputPath,
            //         }
            //     }

            // }
            { test: /\.(ttf|eot|svg|woff|woff2)$/, use: 'url-loader' }, // 处理 字体文件的 loader 
        ]
    },
    devServer: {
        contentBase: config.devServerOutputPath,
        overlay: {
            errors: true,
            warnings: true,
        },
        open: true // 服务启动后 打开浏览器
    }
});
  • build/webpack.prod.js生产配置文件
// 引入基础配置
const path = require('path');
const webpackBase = require('./webpack.base');
// 引入 webpack-merge 插件
const webpackMerge = require('webpack-merge');
// 清理 dist 文件夹
const CleanWebpackPlugin = require('clean-webpack-plugin');
// js压缩、优化插件
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
// 抽取css extract-text-webpack-plugin不再支持webpack4,官方出了mini-css-extract-plugin来处理css的抽取
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
//由于HappyPack 对file-loader、url-loader 支持的不友好,所以不建议对该loader使用
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

const config = require('./config'); // 多页面的配置项
const ASSET_PATH = '//abc.com/static/'; // 线上静态资地址
// 合并配置文件
module.exports = webpackMerge(webpackBase, {
    mode: 'production',
    module: {
        rules: [{
                test: /\.js$/,
                //把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行
                loader: 'happypack/loader?id=happyBabel',
                //排除node_modules 目录下的文件
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader'
                ]
            }, {
                test: /\.scss$/,
                exclude: /node_modules/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'sass-loader',
                    'postcss-loader',
                    {
                        loader: 'sass-resources-loader',
                        options: {
                            resources: path.resolve(__dirname, '../src/styles/lib/main.scss'),
                        },
                    }
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/, // 处理图片
                use: {
                    loader: 'file-loader', // 解决打包css文件中图片路径无法解析的问题
                    options: {
                        // 打包生成图片的名字
                        name: 'list/[name].[hash:8].[ext]',
                        // 图片的生成路径
                        //outputPath: config.imgOutputPath,
                        publicPath: ASSET_PATH
                    }
                }
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/, // 处理字体
                use: {
                    loader: 'url-loader',
                    options: {
                        outputPath: config.fontOutputPath,
                        publicPath: ASSET_PATH
                    }
                }
            }
        ]
    },
    optimization: {
        splitChunks: {
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendor',
                    chunks: 'all'
                }
            }
        },
        minimizer: [
            new UglifyJsPlugin({ // 压缩js
                uglifyOptions: {
                    compress: {
                        warnings: false,
                        drop_debugger: false,
                        drop_console: true
                    }
                }
            }),
            new OptimizeCSSAssetsPlugin({ // 压缩css
                cssProcessorOptions: {
                    safe: true
                }
            })
        ]
    },
    plugins: [
        new HappyPack({
            //用id来标识 happypack处理那里类文件
            id: 'happyBabel',
            //如何处理  用法和loader 的配置一样
            loaders: [{
                loader: 'babel-loader?cacheDirectory=true',
            }],
            //共享进程池
            threadPool: happyThreadPool,
            //允许 HappyPack 输出日志
            verbose: true,
        }),
        // 自动清理 dist 文件夹
        new CleanWebpackPlugin(['dist'], {
            root: path.resolve(__dirname, '..'), // 根目录
            verbose: true, //开启在控制台输出信息
            dry: false, // 启用删除文件
        }),
        new MiniCssExtractPlugin({
            filename: 'css/[name].[chunkhash:8].css'
        })
    ]
});
  • src/mainJs/index.js入口文件
import Vue from 'vue';
import App from '@/App.vue';

Vue.config.productionTip = false
new Vue({
    render: h => h(App),
}).$mount('#app');
  • src/App.vue
<template>
    <div id="app">首页</div>
</template>
<script>
export default {
    
}
</script>
<style>
#app{
    background: burlywood
}
</style>

src/template/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>
        <%= htmlWebpackPlugin.options.title %>
    </title>
</head>

<body>
    <div id="app"></div>
</body>

</html>
  • 如何使用和管理第三方JS库
  1. CDN:标签引入即可
  2. npm 包管理: 目前最常用和最推荐的方法
  3. 本地js文件:一些库由于历史原因,没有提供es6版本,需要手动下载,放入项目目录中,再手动引入。

针对第一种和第二种方法,各有优劣,有兴趣可以看这篇:《CDN 使用心得:加速双刃剑》
针对第三种方法,如果没有webpack,则需要手动引入import或者require来加载文件;但是,webpack提供了alias的配置,配合webpack.ProvidePlugin这款插件,可以跳过手动入,直接使用!

//页面使用
// app.js
$("div").addClass("new");
jQuery("div").addClass("old");

在配置文件中设置
webpack.ProvidePlugin参数是键值对形式,键就是我们项目中使用的变量名,值就是键所指向的库。
webpack.ProvidePlugin会先从npm安装的包中查找是否有符合的库。
如果webpack配置了resolve.alias选项(理解成“别名”),那么webpack.ProvidePlugin就会顺着这条链一直找下去。

 resolve: {
    alias: {
      jQuery$: path.resolve(__dirname, "src/vendor/jquery.min.js")
    }
  },
plugins: [
    new webpack.ProvidePlugin({
      $: "jquery", // npm
      jQuery: "jQuery" // 本地Js文件
    })
  ]

猜你喜欢

转载自blog.csdn.net/weixin_43554698/article/details/90035946