webpack4 搭建企业级脚手架

前端模块化

commjs使用同步的方式去加载模块,使用module.export导出,require引入,commjs适合用于在服务端, 由于服务端读取磁盘文件较快,commjs同步的特性让其在编译完成就可使用模块了。commjs的加载机制是 require的值是module.export的值的拷贝, amd和cmd都是使用的异步的加载机制,主要用于浏览器端。 es6模块是ECMA 提出的javascript模块化的规范,将会成为一种通用解决方案。 commjs的加载方式是运行时加载, import是编译时加载, 编译时加载能够作指定输入,编译阶段作ast语法树解析能够进行摇树优化,还能够作静态优化的方案,而编译时加载则不能,由于他是值的拷贝。javascript

项目构建工具

市面上有不少如rollup,webpack, glup还有Parcel。 rollup专一于处理es mudule的处理,若是要实现其余模块的打包会比较繁琐和复杂,对一些splictChunk的方式支持的并不友好,不支持HMR, rollup更加精而美,如vue、react这些类库的源码都是经过rollup进行构建。相比较来讲, rollup更适合类库的搭建,而webpack适合全部项目的构建。 Parcel构建工具在必定意义上实现了零配置便可快速构建项目。配置过程依赖自动安装,并且自带多进程工做方式。可是仍是更加推荐webpack,由于webpack是目前为止最好的构建工具, 在知名度,生态环境,插件市场,还有开发人员的熟知度。这每一点都无比的重要,而webpack是在每一个点上都作到首屈一指的水平,除了它的官方文档之外。css

构建工具的做用

打包各类类型的资源, 将代码中分散的js打包到bundle.js中,将一些新特性的语法转换为载体(浏览器)能够识别的语法, 代码转换,文件优化,代码分割, 模块合并, 自动刷新, 代码校验, 自动发布。 构建让开发流程更加天然,合理的构建让开发效率更加高效,提升效率和生产力, 目前的webpack还须要手动构建, 但愿等到之后会出现一套集成的通用方案,接口暴露的更完全,更友好。 就像之前的java开发使用ssm开发项目,须要进行一大堆的配置,配置过程痛苦并且繁琐,到了springboot的时代,就没有那么繁琐的配置了。前端构建工具的发展也会随着一系列的技术迭代而愈来愈友好。html

了解webpack

webpack4提供了webpack-cli 启动webpack,而此时webpack也能够进行零配置。会给一些必要的配置设置默认值。好比entry默认src/index.js, output默认build/main.js。前端

webpack的配置文件经过webpack.config.js进行修改,配置文件使用commonjs的方式导出配置数据。vue

mode模式

mode模式, develpoment 和 production webpack会根据开发模式的不一样进行不一样的项目优化, 而配置人员要针对不一样的环境进行不一样的构建流程, 开发环境须要添加一些辅助开发工具和开发打包的一些优化方案,而生产环境则更注重于体积的减少和压缩模块的拆分与公用。java

入口和出口

入口和出口,入口指定了从哪一个文件开始来进行构建,bundle是webpack将全部在代码中零散的模块进行统一打包,打包到一个bundle中,这个bundle的输出位置和其余输出信息。而输出位置信息必须是一个绝对路径。node

打包后的模块是一个自执行函数,传入的参数是一个对象,key是打包文件的路径,值是一个函数,使用eval函数执行其内部逻辑。react

  • webpack为每个模块会标识一个moduleId, 因此咱们重复引用的时候并不会进行重复打包,就是多执行一次__webpack_require__(moduleId)拿到installedModules这个中的module cache

context配置

context 配置,context的值是一个路径, 该路径是entry的相对路径也是html-webpack-plugin的相对路径, 也就是说当咱们在context上下文中配置了一个路径,那么entry与htmwebpack-plugin的路径就是基于该路径的,webpack

resolve配置

resolve配置 能够用来配置别名,当在代码中使用import 引入某个文件的时候可使用这个别名进行路径的匹配。还能够对引入文件的扩展名进行简写,好比引入import ../index.js能够简写为 import ../indexgit

resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
        'vue$': 'vue/dist/vue.esm.js',
        '@': resolve('src'),
    }
},
复制代码

对媒体资源进行打包

对一些媒体资源进行打包,使用url-loader,也可使用file-loader,可是url-loader几乎有file-loader的全部功能,还能够配置超过必定大小转换base64, 在url-loader的name属性中有可让咱们自定义文件输出的路径,能够进行对打包后文件的分类和管理。name的值就是打包事后的文件路径和文件名称,文件名称使用[name]占位符进行替代,文件类型使用[ext]占位符替代 这里的媒体资源通常指

// 注意这里打包图片的时候,要设置esModule: false,是版本问题,否则会报错。 
        png|jpe?g|gif|svg
        mp4|webm|ogg|mp3|wav|flac|aac
        // 注意若是你使用了elementui的话,这里是woff, 而不是woff2, 由于element-ui/lib/theme-chalk内是没有woff2文件的。
        woff|eot|ttf|otf
        
复制代码

例如:

{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    exclude: [resolve('src/icons')],
    include: [resolve('src')],
    options: {
        esModule: false, // 版本问题
        limit: 8192,
        // 自定义目录和名称
        name: utils.assetsPath('img/[name].[hash:7].[ext]') // 打包后的文件会输出到指定目录的img/[name].[hash:7].[ext]
    }
},
复制代码

对svg的打包使用svg-sprite-loader

devServer的配置

在development模式中的配置,devServer是很是重要的一个辅助开发工具,提供HMR, gzip压缩,使用http服务运行,自动打开浏览器,跨域代理,对控制台的输出控制,静态资源的路径一系列的功能, 使用devServer开启的服务并无产出任何文件,devServer将打包后的结果存放到内存中,在内存中进行对文件的读取, 极大的提升了效率。devServer内部也是内置了一个express服务器, devServer的contentBase放置没有被webpack打包的文件,可是若是也须要进行访问的静态资源目录,并且使用服务器路由进行指向,不须要太多文件的输入和输出操做。

devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: {
        rewrites: [
            { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
        ],
    },
    hot: true,
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    // open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
        ? { warnings: false, errors: true }
        : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,
    quiet: true, // necessary for FriendlyErrorsPlugin
    watchOptions: {
        poll: config.dev.poll,
    }
},
复制代码

对样式文件进行打包

webpack对于loader的处理是自下而上的,在处理.css文件时,使用了两个loader css-loader和vue-style-loader(style-loader),这里用的是vue项目,而要解析.vue文件中的style样式,要使用vue-style-loader进行处理。 若是对于预处理样式文件的话,好比sass文件, 在加上sass-loader处理一遍再给css-loader....。

关于cache-loader的坑

在后面优化的时候,使用了cache-loader进行优化,cache-loader的用法就是写在loader的最前面,而后进行cache-loader解析,若是没有改变的,直接从磁盘上拿文件,极大提高了效率,可是解析和读取文件的过程也须要时间,因此若是是性能开销极大的loader选择cache-loader进行处理的话,会显著的提升打包效率。可是若是遇到了mini-css-extract-plugin.loader的话,仍是将cache-loader放在mini-css-extract-plugin.loader的前面,那么根本就不会去打包样式,去cache-loader的issues中找到了这样一个issue github.com/webpack-con… 你要将cache-loader写在mini-css-extract-plugin.loader的后面。

  • ps

若是插入太多的style样式会致使页面阻塞,因此使用mini-css-extract-plugin对样式进行提取。可是在开发环境中,我使用mini-css-extract-plugin没法进行css热更新,网上找了几种办法未解决,使用了style标签的形式能够进行热更新。在生产环境中使用mini-css-extract-plugin。

对js文件进行打包

webpack默认只能处理js和json, 而对于js的处理又是重中之重,咱们须要把js转换成es2015, 转换jsx语法,对一些类的静态方法须要进行polyfile, 对于generator语法的处理,js的分包,懒加载机制都是js文件须要作的,经过babel他们变的简单了。像一些经过@babel/ployfile不只体积很大,并且会污染全局的沙盒环境,很不推荐。这里使用core-js进行对类的方法进行polyfile

{
      "presets": [
        [
          "@babel/preset-env",
          {
            "useBuiltIns": "usage", // or "usage"
            "corejs": 3
          }
        ]
      ],
      "plugins": [
        "transform-vue-jsx",
        "@babel/plugin-transform-runtime",
        "@babel/plugin-syntax-dynamic-import"
      ]
    }
复制代码
由于js模块较多,就采用了多线程打包的方案,对js文件进行多线程打包,这里视状况而定,有些项目不必定须要多线程打包,由于分配线程须要消耗必定的资源,
若是电脑性能好,开的进程少,那么打包的速度就会提升,反之会下降。
happypack的使用很简单,匹配到js文件,使用happypack进行打包,指定id,该id会去找plugin中的HappyPack中的id,

对于js使用cache-loader,发现会很大程度上提升打包的速度,配合happypack大概提升8s左右的时间,
这里的时间不是固定的,是根据项目的大小和复杂程度来的,在必定程度上成正比。
复制代码
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
new HappyPack({
    //用id来标识 happypack处理那里类文件
    id: 'js',
    //如何处理 用法和loader 的配置同样
    loaders: [
        {
            loader: 'cache-loader'
        },
        {
            loader: 'babel-loader?cacheDirectory=true',
        }
    ],
    //共享进程池
    threadPool: happyThreadPool,
    //容许 HappyPack 输出日志
    verbose: true,
}),
复制代码
关于happypack对vue文件的多线程打包
vue-loader 15如今不支持happypack 由于happypack内部使用了虚拟的compiler和loaderContext,不建议使用happypack了,
复制代码

热模替换

永远不要再生产环境下使用HMR, 因此咱们只在开发环境中配置。

  • 所谓的热模替换,即对模块进行热插拔,当更新的时候只对变化的模块进行更新,而不会干扰到程序的总体运行流程和所有刷新,咱们知道刷新意味着内存中数据丢失,须要从新获取资源,在不少场景中,HMR都很重要。
// devserver中配置
 // 开启 HMR 特性,若是资源不支持 HMR 会 fallback 到 live reloading
 hot: true,
 // 表示不会回退到 使用刷新的方式进行重载
 // hotOnly: true
 
 // 配置hmr所须要的插件
 new webpack.HotModuleReplacementPlugin(),
复制代码
  • 对于通常的css和img,hmr开箱即用,可是对于js,hmr却没法进行通用方案的支持。由于js导出模块的不肯定性和引用的不肯定性,hmr没法对其进行通用的处理方案。若是要对js进行进行热模替换的话,则须要HotModuleReplacementPlugin提供的api进行手动的处理须要进行hmr的模块。可是所幸咱们使用的是框架,无论是vue仍是react都集成了hmr的方案,他们会让这些js模块进行统一处理,加上moudle.hot方法进行判断。咱们只须要开箱即用。在vue中 vue-loade 内部使用的 vue-hot-reload-api会集成进去热重载方案,若是使用原生js的话,那么就须要咱们进行手动的对js进行热莫替换, 框架作了一套集成的方案,一样对于模块的导出方式和数据结构也是规定好的,这就是为何框架这么方便的集成一套通用的热模替换方案。

配套插件

// 重载以后在控制台显示的file name 是更新的文件的名字,默认的是文件的id
new webpack.NamedModulesPlugin(), 
复制代码

sourcemap

  • 在webpack中 sourcemap的模式的要使用devtool进行开启, 这里官网上已经说的很详细了,

    补充几个小点:eval模式放在一个临时的虚拟机中运行,VM154:1 // 
    复制代码

  • 每段代码都会经过sourceURL进行声明

  • eval模式,构建速度很快,能够获得错误在源代码中的行列,可是没法查看具体的行列信息, 由于它没法查看源代码,只能查看转换后的代码。

  • source-map模式 , 会产生.map文件,将打包好的文件经过.map文件能够映射到源文件,

  • eval-source-map 能够定位文件,并且能够定位具体的行列信息(由于能够生成source-map映射文件),

  • cheap-eval-source-map 能够定位文件,可是没法定位到具体的列,只能定位到具体的行,并且是loader转换以后的代码,可是构建速度获得提升

  • cheap-module-eval-source-map 和 cheap-eval-source-map 相似,可是是loader转换以前的源文件代码。

在开发环境中devtool通常状况下项目的最适合的选择的是cheap-module-eval-source-map 他的打包和构建速度能够,并且会产品源代码,能够进行调试,生产环境使用false,构建和打包速度最快,不会产生源代码和其余额外文件。若是在生产环境中出现了bug,而其余环境中没有这个bug,想要定位并且还不想暴露源代码的话,可使用nosources-source-map

开发环境优化

dll优化: 在项目中,咱们一般会使用到一些第三方包,并且这些第三方包版本固定,不会迭代更新,而咱们每次打包都要这些第三方包进行打包,很浪费时间,因此开发环境咱们使用dll连接库进行对一些固定的文件进行先行打包,而后使用manifest进行引入,咱们先新建dll_webpack配置文件(配置development模式), 而后在开发环境须要的webpack配置文件中使用webpack自带的DllReferencePlugin插件进行桥接已经打包好的第三方资源。 咱们须要在index.html中手动引入已经打包好的dll.js文件,咱们能够经过AddAssetHtmlPlugin插件将打包好的js文件动态的导入到index.html中

// webpack.dll.config.js
const webpack = require('webpack')
const utils = require('./utils')
const config = require('../config')
const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    hk: ['vue/dist/vue.esm.js', 'vuex', 'echarts', 'vue-router', 'element-ui']
  },
  output: {
    filename: '_dll_[name].js',
    // path: path.resolve(__dirname, 'dll'),
    path: path.resolve(__dirname, 'dll'),
    library: '_dll_[name]',
    // libraryTarget: 'var'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '_dll_[name]',
      // path: path.resolve(__dirname, 'dll', 'manifest.json')
      path: path.resolve(__dirname, 'dll', 'manifest.json'),
    })
  ]
}
// webpack.config.js
// 将dll连接库的内容放到html中
new AddAssetHtmlPlugin({ filepath: require.resolve('./dll/_dll_hk.js') }),
new webpack.DllReferencePlugin({
    manifest: path.resolve(__dirname, 'dll', 'manifest.json')
}),
复制代码

生产环境优化

使用mini-css-extract-plugin时压缩代码

当使用mini-css-extract-plugin进行提取css文件时, css文件不会被打包, 咱们可使用在minimizer中使用optimize-css-assets-webpack-plugin 进行压缩css代码,可是当使用minimizer属性的时候,webpack就会认为你再也不使用默认的压缩方式了,而是使用自定义的压缩方式。因此还要手动的添加webpack内置的压缩js的插件terser-webpack-plugin
复制代码
optimization: {
    minimize: true, // production模式会自动开启摇树优化 usedExports 和 minimize 和 sideEffects 
    splitChunks: {
        chunks: "all",
        minSize: 16000, // 模块的最小体积
        minChunks: 1, // 模块的最小被引用次数
        maxAsyncRequests: 5, // 按需加载的最大并行请求数
        maxInitialRequests: 3, // 一个入口最大并行请求数
        automaticNameDelimiter: '~', // 文件名的链接符
        name: true,
        cacheGroups: { // 缓存组
            vendors: {  //第三方
                test: /[\\/]node_modules[\\/]/,
                priority: -10//权重
            },
            default: {
                minChunks: 2,
                priority: -20,
                reuseExistingChunk: true
            }
        }
    },
    minimizer: [
        new TerserJSPlugin({
            cache: true, // 是否缓存
            parallel: true, // 是否并行打包
            sourceMap: false // 是否开始源码映射
        }),
        new OptimizeCSSPlugin({})
    ],
},
复制代码
  1. tree-shaking 摇树优化 摇树也就是将晃动将树上的枯黄的树叶让其掉落下来,造成一颗新树。 而webpack会将那些未被引用的代码经过摇树优化进行去除。tree-shaking配合optimization 选项开启minimize会自动压缩掉未被使用的代码。 对于webpack来说,并非全部的模块代码均可以被tree-shaking,一个较典型的就是loadsh,若是你引入了loadsh,只用了其中一个方法,webpack仍然会将loadsh其余的方法所有打包,tree-shaking失效了,由于loadsh默认的导出方式是commonjs,而tree-shaking的前提就是模块是esModule。
  2. scope hoisting 若是没有scope hoisting的话,打包以后在一个自执行函数中会存在一些变量,这些变量的做用就是通过一系列的计算对另一个结果产生影响。而这些东西运行在浏览器端会消耗内存进行存储,有可能还会产生大量的闭包(须要规定函数做用域)形成内存消耗,而scope hoisting能够将这些静态放在一个做用域中,进行合并计算。减小了打包的体积和减小了内存的开销。
  3. splitChunks 对代码进行分包, splitchunk会将咱们项目中的资源模块按照设计的规则打包到不一样的bundle中。 在项目中主要用到的就是与ESModules 的动态导入特性相结合,按需加载模块。并且能对代码公共部分进行提取。对第三方vendors和业务代码进行提取
相关文章
相关标签/搜索