解析webpack

解析webpack

Posted by SkioFox on December 28, 2017

在前端开发中目前webpack已经是最流行的项目构建和打包工具,其优秀的模块化支持,编译,热更新,代码分割,文件处理,打包等功能深受大家喜爱。使用webpack也有一段时间了,现在整理下自己对webpack的理解。

avatar

上图列举了webpack在前端领域中的应用场景,可以看出已经形成非常全面的工具了, 下面我们将通过示例的方式详解webpack的应用场景。

核心概念

  1. Enrty
    • 打包的入口,代码的入口,多个或者单个
            module.exports = {
                entry: {
                    index: ['index.js','app.js'],
                    vendor: 'vendor.js'
                }
            }
      
  2. Output
    • 打包成的文件(bundle)/一个或者多个/自定义规则
            module.exports = {
                entry: {
                    index: ['index.js','app.js'],
                    vendor: 'vendor.js'
                },
                output: {
                    path: `${__dirname}/build/static/`  // __dirname绝对路径
                    filename: '[name].min.[hash:5].js'
                }
            }
      
  3. Loaders
    • 处理除js的其他文件,并将其转化成js模块
            module.exports = {
                module: {
                    rules: [
                        {
                            test: /\.css$/,
                            use: 'css-loader'
                        }
                    ]
                }
            }
      
    • loader的种类
      • 编译相关:babel-loader、ts-loader
      • 样式相关:style-loader、css-loader、less-loader、postcss-loader、stylus-loader
      • 文件相关:file-loader、url-loader
  4. Plugins
    • Plugins的作用
      • 参与打包整个过程
      • 打包优化和压缩
      • 配置编译时的变量
          const webpack = require('webpack');
          module.exports = {
              plugins: [
                  // 混淆和压缩代码
                  new webpack.optimize.UglifyJsPlugin()
              ]
          }
        
    • Plugins的种类
      • 优化相关
        • CommonsChunkPlugin:提取不同chunk之间的相同代码,提取出单独代码块
        • UglifyJsWebpackPlugin:压缩和混淆代码
      • 功能相关
        • ExtractTextWebpackPlugin:提取css为单独的css文件
        • HtmlWebapckPlugin:生成html
        • HotModuleReplacementPlugin: 热更新
        • CopyWebpackPlugin: 拷贝文件,如第三方已打包的文件
  5. webpack中的名词
    • chunk:表示webpack中的代码块
    • Bundle: 代表打包后的文件
    • Module: 模块

      使用webpack

  6. webpack命令方式:打包js

    Usage without config file: webpack <entry> [<entry>] <output>

  7. webpack配置:
    • 打包js

      webpack --config webpack.config.js

    • 编译ES6、ES7、TS

      npm install babel-loader babel-core babel-preset-env babel-plugin-transform-runtime ts-loader awesome-typescript-loader -D

      npm install babel-polyfill babel-runtime -S

      • Babel
      • Babel-presets:设置编译的浏览器适应版本
      • Babel-plugin
        • Babel Polyfill: 统一浏览器的全局垫片,会在全局引用和处理ES新语法,为引用做准备

          import "babel-polyfill"

        • Babel Runtime Transform: 局部垫片,为框架做准备

          在项目根目录下.babelrc配置babel相关的内容:如target、presets、plugin等

        • typescript-loader

          在根目录配置tsconfig.json

    • 提取公共代码
            module.exports = {
                plugins: [
                    new webpack.optimize.CommonsChunkPlugin({
                        name: 'common',
                        minChunks:2
                    })
                    new webpack.optimize.CommonsChunkPlugin({
                        name: ['vendor','manifest'],
                        minChunks:Infinity
                    })
                    new webpack.optimize.CommonsChunkPlugin({
                        async: 'async-common',
                        children: true,
                        minChunks:Infinity
                    })
                ]
            }
      
    • 代码分割和懒加载
      1. webpack内置方法:require.ensure/require.include
      2. ES2015 Loader spec: System.import -> import() 返回的是Promise
        // 动态加载模块
            import(
                /*webpackChunkName: async-chunk-name*/
                /*webpackMode: lazy*/
                modulename     
            )
      
      1. 代码分割的场景
        • 分离业务代码和第三方依赖
        • 分离业务代码和业务公共代码和第三方依赖
        • 分离首次加载和访问后加载的代码
            // pageA.js 异步加载lodash
            // 第一个lodash是引入但并不会执行,第二个lodash才是执行
            require.ensure(['./lodash'],function () {
                var _ = require('lodash')
                _.join([1,2,3])
            },vendor)
          
            // 动态import加载
            import(/* webpackChunkName: 'lodash' */'./lodash').then(function(){
                console.log("test")
            })
          
    • 处理css
        // 配置css相关loader处理css文件
            module.exports = {
                module: {
                    rules: [
                        {
                            test: /\.css$/,
                            use: [
                                /*{
                                    loader: 'style-loader',
                                    options: {
                                        insertInto: '#app',
                                        singleton: true,
                                        transform: './css.transform.js'
                                    }
                                },
                                {
                                    loader: 'css-loader',
                                    options: {
                                        // minimize: true,
                                        modules: true,
                                        // 定义样式名称
                                        localIdentName: '[path][name]_[local]_[hash:base64:5]'
                                    }
                                }*/
                                /*通过css的link标签插入*/
                                /*{
                                    loader: 'style-loader/url'
                                },
                                {
                                    loader: 'file-loader'
                                }*/
                                /*控制样式是否使用use()和unuse()方法*/
                                {
                                    loader: 'style-loader/useable'
                                },
                                {
                                    loader: 'css-loader'
                                }
                                                                ]
                        }
                    ]
                }
            }
      
            module.exports = {
                rules:[
                    {
                        test:/\.less$/,
                        // use是从后往前就行处理
                        // extract-text-webpack-plugin用于提取css文件
                        use: ExtractTextWebpackPlugin.extract({
                            // 不提取css的处理方式
                            fallback: {
                                loader: 'style-loader',
                                options: {
                                    insertInto: '#app',
                                    singleton: true,
                                    transform: './css.transform.js'
                                }
                            },
                            // 处理css的loader
                            use:[
                                {
                                    loader: 'css-loader',
                                    options: {
                                        minimize: true,
                                        modules: true,
                                        localIdentName: '[path][name]_[local]_[hash:base64:5]'
                                    }
                                },
                                {
                                    loader:'less-loader'
                                }
                            ]
                        })
                    },
                    {
                        // 引入模块的loader
                        test: path.resolve(_dirname,'src/app.js'),
                        use: [
                            {
                                loader: 'imports-loader',
                                options: {
                                    $: 'jquery'
                                }
                            }
                        ]
                    }
                ]
                plugins: [
                    // extract-text-webpack-plugin用于提取css文件
                    new ExtractTextWebpackPlugin({
                        filename: '[name].mini.css',
                        // 为true时会提取所有的css模块
                        allChunks: false
                    })
                ]
            }
      
    • PostCss in webpack
      • PostCss
        1. 用js去转化css的工具,可以自定义处理css的规则
               // 安装
               npm install postcss postcss-loader autoprefixer css-nano css-next
          
               module.exports = {
                   module: {
                       rules: [
                           {
                               test: /\.less$/,
                               use: [
                                   {
                                       loader: 'style-loader',
                                       options: {
                                           insertInto: '#app',
                                           singleton: true,
                                           transform: './css.transform.js'
                                       }
                                   },
                                   {
                                       loader: 'css-loader',
                                       options: {
                                           // minimize: true,
                                           modules: true,
                                           // 定义样式名称
                                           localIdentName: '[path][name]_[local]_[hash:base64:5]'
                                       }
                                   },
                                   // postcss用在less/sass和css loader之间
                                   {
                                       loader: 'postcss-loader',
                                       options: {
                                           // 说明plugin时给postcss使用 
                                           ident: 'postcss',
                                           plugins: [
                                               // 可以在顶部引入和调用
                                               //  require('autoprefixer')()
                                               require('postcss-cssnext')()
                                               // 合成雪碧图
                                               require('postcss-sprites')({
                                                   // 生成的sprites
                                                   spritePath: 'dist/assets/imgs/sprites',
                                                   // 处理高清的图片
                                                   // 将图片命名为:1@2x.png/2@2x.png/2@2x.png
                                                   retina: true
                                               })
                                           ]
                                       }
                                   }
                                   {
                                       loader:'less-loader'
                                   }
                                                  
                               ]
                           }
                       ]
                   }
               }
          
      • Autoprefixer
        1. 给css加入浏览器兼容的前缀
      • CSS-nano
        1. 优化和压缩css,同css-loader中minimix属性
      • CSS-next
        1. 可以使用css的新语法会转化成浏览器可识别的语法
      • browserslist
        1. 在package.json中配置公共的浏览器的版本要求,所有都公用一个配置
           "browserslist": [
               ">= 1%",
               "last 2 versions"
           ]      
          
        2. .browserslistrc 单独配置文件
    • Tree Shaking(打包时去除未用到的js和css)
      1. 使用场景:引入第三方库或者常规优化时需要使用
      2. JS Tree Shaking和CSS Shaking
         module.exports = {
             plugins: [
                 // 去除css
                 new PurifyCSS({
                     paths: glob.sync([
                         /*
                         path.resolve(_dirname,'./index.html')
                         */
                         // 在src下新建模板文件情况下
                         path.join(_dirname,'./*.html'),
                         path.join(_dirname,'./src/*.js')
                     ])
                 })
                 // 去除未用到的js代码
                 new webpack.optimize.UglifyJsPlugin()
             ]
         }
        
    • 图片文件处理
      1. webpack对图片处理的使用场景及优化
        • CSS引入图片background-image -> file-loader
        • 自动合成雪碧图 -> postcss-sprites
        • 压缩图片 -> img-loader
        • 小图片转base64 编码 -> url-loader
            module.exports = {
                module: {
                    rules: [
                        {
                            test: /\.(png|jpg|jpeg|gif)$/,
                            use: [
                                    /*{
                                        // 处理和加载图片
                                        loader:'file-loader',
                                        options: {
                                            publicpath:'', 
                                            outputPath: 'dist/',
                                            useRelativePath: true
                                        }
                                    },*/
                                    {
                                        // 将图片转化为base64 作为url载入url-loader可以替代file-loader
                                        loader: 'url-loader',
                                        options:{
                                            // 配置生成的文件名
                                            name:'[name].min.[ext]',
                                            // 图片大小限制
                                            limit:10000,
                                            publicpath:'', 
                                            outputPath: 'dist/',
                                            useRelativePath: true
                                        }
                                    },
                                    {
                                        // 压缩图片
                                        loader:'img-loader',
                                        options: {
                                            // 压缩格式配置
                                            pngquant: {
                                                quality: 80
                                            }
                                        }
                                    }
                                ]
                        }
                    ]
                }
            }
          
    • 字体文件处理(file-loader/url-loader)
            module.exports = {
                module: {
                    rules: [
                        {
                            test: /\.(eot|woff2?|ttf|svg)$/,
                            use: [
                                    {
                                        // 会打包到css的base64
                                        loader: 'url-loader',
                                        options: {
                                                name:'[name].min.[ext]',
                                                limit:5000,
                                                publicpath:'', 
                                                outputPath: 'dist/',
                                                useRelativePath: true
                                        }
                                    }
                                ]
                        }
                    ]
                }
            }
      
    • 第三方js库处理
      1. 场景:
        • 第三方库在远程的CDN上:通过script标签将cdn地址迁到页面中,然后在源代码中使用即可
        • 在本地源代码的第三方库或者通用库:使用webpack.providePlugin或者imports-loader注入
                module.exports = {
                    resolve: {
                        alias: {
                            // 加$代表解析jquery关键词解析到某个文件下,确切匹配
                            jquery$: path.resolve(_dirname,'src/libs/jquery.min.js')
                        }
                    },
                    plugins: [
                        // 在模块中注入第三方库,不用每次使用都import
                        // 这是通过npm安装
                        /*new webpack.ProvidePlugin({
                            $: 'jquery'
                        })*/
                        // 本地源代码依赖的库需要配置路径
                        new webpack.ProvidePlugin({
                            $: 'jquery'
                        })
                    ]
                }
          
    • HTML中引入图片进行压缩
            module.exports = {
                module: {
                    rules: [
                        {
                            test: /\.html$/,
                            use: [
                                {
                                    loader: 'html-loader',
                                    options: {
                                        // 对img进行处理
                                        attrs: ['img:src','img:data-src']
                                    }
                                },
                            ]
                        }
                    ]
                }
            }
      
    • 配合优化
      • 提前载入webpack加载代码,如用script标签将公共代码插入到html中,从而减少请求。
        • inline-manifest-wepack-plugin
        • html-webpack-inline-chunk-plugin(和html-loader配合)
                module.exports = {
                    rules: [
                        {
                            test: /\.js$/,
                            use: [
                                {
                                    loader: 'babel-loader',
                                    options: {
                                        preset: ['env']
                                    }
                                }
                            ]
                        }
                    ]
                    plugins: [
                        // 提取公共代码
                        new webpack.optimize.CommonsChunkPlugin({
                            name: 'manifest',
                        })
                        // 将公共代码用script提前插入html
                        new HtmlInlineChunkPlugin({
                            inlineChunks: ['manifest']
                        })
                        new HtmlWebpackPlugin({
                            filename: 'index.html',
                            template: './index.html',
                            minify: {
                                collapseWhitespace: true
                            }
                        })
                        // 每次打包清除dist的插件
                        new CleanWebpackPlugin(['dist'])
                    ]
                }
          
          
  8. webpack开发环境搭建
    • 搭建本地服务器开发环境可以模拟真实的web服务器环境, 可以更好的调试代码。
    • webpack watch mode:只能监听, 没有服务器
            // 命令
            webpack -w --progress --display-reasons --color
      
    • webpack-dev-server:官方提供的服务器
      • live reloading:刷新浏览器
      • 路径重定向
      • 支持https
      • 浏览器显示编译错误
      • 接口代理
      • 模块热更新(不刷新浏览器的情况下更新)
      • devServer字段模式(inline/contentBase/port/historyApiFallback/https/proxy/hot/openpage/lazy/overlay)
          module.exports = {
              // 配置webpack-dev-server
              // 在 package.json配置:"serve": "webpack-dev-server --watch --inline --open“
              // 完整配置:"serve": "webpack-dev-server --watch --inline --open --config webpack--dev-server.js"
              devServer: {
                  // 不刷新浏览器
                  // inline:false,
                  port: 9001,
                  // 解决h5 history 导致的404
                  // historyApiFallback: true // 所有的路径都被重置到了首页
                  historyApiFallback: {
                      // 跳转规则
                      rewrites: [
                          {
                              // from: 'pages/a',
                              // to: 'pages/a.html'
                              // 配置rewrite规则
                              httpAcceptHeaders: ['text/html','application/xhtml+xml'],
                              // 正则匹配
                              from: /^\/([a-zA-Z0-9]+\/?)([a-zA-Z0-9])/,
                              to:function (context){
                                  return '/' + context.match[1] + context.match[2] + '.html'
                              }
                          }
                      ]
                  },
                  // 代理远程接口请求:http-proxy-middleware(options:target/changeOrigin/headers/logLevel/pathRewrite)
                  proxy: {
                      // 匹配路径
                      '/api':{
                          // 实际请求的地址
                          target: 'https://m.weibo.cn',
                          // 改变源到url
                          changeOrigin: true,
                          // 请求携带header,增加header内容,请求需要身份验证的接口
                          headers: {
                              Cookie: 'xxxx'
                          },
                          // 重定向接口请求,方便书写
                          pathReWrite:{
                              '^/comments': '/api/comments'
                          },
                          // logLevel: 'debug',
                          // 热更新:webpack.HotModuleReplacementPlugin/webpack.NamedModulesPlugin
                          // 好处:保持应用的数据状态/节省调试时间/样式调试更快
                          hot: true
                      }
                  }
              }
          }
        
    • express + webpack-dev-middleware:灵活配置
            //安装依赖:express/koa opn webpack-middleware webpack-hot-middleware http-proxy-middleware connect-history-api-fallback
            // 配置server.js
            const express = require('express')
            const webpack = require('webpack')
            // 打开浏览器调试
            const opn = require('opn')
                  
            const app = express()
            const port = 3000
      
            const webpackDevMiddleWare = require('webpack-dev-middleware')
            const webpackHotMiddleWare = require('webpack-hot-middleware')
            const proxyMiddleWare = require('http-proxy-middleware')
            const historyApiFallback = require('connect-history-api-fallback')
      
            // 将开发环境的配置载入
            const config = require('./webpack.base.conf')('development')
            const compiler = webpack(config)
      
            // 载入proxy和historyApiFallback配置
            const proxyTable = require('./proxy')
      
            for (let context in proxyTable) {
                app.use(proxyMiddleware(context, proxyTable[context]))
            }
      
            app.use(historyApiFallback(require('./historyApiFallback')))
      
            // 使用中间件   
            app.use(webpackDevMiddleWare(compiler,{
                publicPath: config.output.publicPath
            }))
            app.use(webpackHotMiddleWare(compiler))
      
      
            app.listen( port, function() {
      
                console.log('listen to localhost:' + port)
                // 打开
                opn('http://localhost:' + port)
            })
      
                  
      
    • Source Map 调试:会将生成的打包代码和原始的代码进行映射
      • JS Source Map (Devtool/webpack.SourceMapDevToolPlugin/webpack.EvalSourceMapDevToolPlugin)
      • CSS Source Map
    • Eslint 检查代码格式(eslint/eslint-loader/eslint-plugin-html/eslint-friendly-formatter)
      • 在webpack config新增eslint-loader
      • 在根目录下建立 .eslintrc 文件配置书写规则
      • 在package.json在eslintConfig中书写规则
      • JS Standard Style https://standardjs.com/ (eslint-config-standard/eslint-plugin-promise/eslint-plugin-standard/eslint-plugin-import/eslint-plugin-node/eslint-config-xx)
          module.exports = {
              module: {
                  rules: [
                      test: /\.js$/,
                      include: [path.resolve(_dirname,'src')],
                      exclude: [path.resolve(_dirname,'src/libs')]
                      use: [
                          {
                              loader: 'babel-loader',
                              options: {
                                  presets: ['env']
                              }
                          },
                          {
                              loader: 'eslint-loader',
                              options: {
                                  formatter: require('eslint-friendly-formatter')
                              }
                          }
                      ]
                  ]
              }
          }
        
          // .eslintrc.js
          module.exports = {
              root: true,
              extends: 'standard',
              plugins:[
        
              ],
              // 全局变量
              globals: {
                  $: true
              }
              rules: {
                  // 缩进
                  indent: ['error',4],
                  // 换行
                  'eol-last': ['error',never]
              },
              env: {
                  browsers: true
              }
          }
        
    • 区分开发环境和生产环境(webpack-merge -> webpack.dev.conf.js/webpack.prod.conf.js/webpack.base.conf. js)
      • 开发环境
        • 模块热更新
        • sourceMap
        • 接口代理
        • 代码规范检查
      • 生产环境
        • 提取公用代码
        • 压缩混淆
        • 文件压缩或者base 64编码
        • 去除无用代码
      • 共同点
              // webpack.base.conf.js
              const productionConfig = require('./webpack.prod.conf')
              const developmentConfig = require('./webpack.dev.conf')
        
              const merge = require('webpack-merge')
        
              const generateConfig = env => {
                  return {
                      // 公共的配置部分
                      // 公共的部分如果需要环境判断则用env去判断,多个loader判断使用concat连接
                  }
              }
              module.exports = env => {
                  let config = env === 'production' ? productionConfig : developmentConfig
        
                  return merge(generateConfig(env), config)
              }
        
        • 同样的入口
        • 同样的代码处理(loader处理)
        • 同样的解析配置
  9. webpack应用优化
    • webpack打包结果分析
      • Offical Analyse Tool
        • mac webpack --profile --json > stats.json
        • windows webpack --profile --json | Out-file 'stats.json' - Encoding OEM

          上传stats.json到http://webpack.github.io/analyse/

      • webpack-bundle-analyzer
        • 插件方式(BundleAnalyzerPlugin)
                var webpack = require('webpack')
                var path = require('path')
                var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
          
                module.exports = {
                    entry: {
                        'pageA': './src/pageA',
                        'pageB': './src/pageB',
                        // 移除lodash测试
                        // 'vendor': ['lodash']
                    },
          
                    output: {
                        path: path.resolve(__dirname, './dist'),
                        publicPath: './dist/',
                        filename: '[name].bundle.js',
                        chunkFilename: '[name].chunk.js'
                    },
          
                    plugins: [
                        new BundleAnalyzerPlugin(),
                        new webpack.optimize.CommonsChunkPlugin({
                            names: ['vendor'],
                            minChunks: 2
                        })
                    ]
                }
          
        • 命令行方式 webpack-bundle-analyzer stats.json
    • 打包速度优化(文件多,页面多,依赖多)
      • 分开vendor代码和app代码
        • DllPlugin
                // 专门打包第三方组件的webpack.dll.conf配置
                const path = require('path')
                const webpack = require('webpack')
          
                module.exports = {
                    entry: {
                        vue: ['vue', 'vue-router'],
                        ui: ['element-ui']
                    },
          
                    output: {
                        path: path.join(__dirname, '../src/dll/'),
                        filename: '[name].dll.js',
                        library: '[name]'
                    },
          
                    plugins: [
                        new webpack.DllPlugin({
                            path: path.join(__dirname, '../src/dll/', '[name]-manifest.json'),
                            name: '[name]'
                        }),
          
                        new webpack.optimize.UglifyJsPlugin()
                    ]
                }
          
          
        • DllReferencePlugin
                const HappyPack = require('happypack')
                module.exports = {
                    plugins: [
                        // 项目内容少的时候可能并不明显
                        new HappyPack({
                        id: 'vue',
                        loaders: [{
                            loader: 'vue-loader',
                            option: require('./vue-loader.conf')
                        }]
                        }),
          
                        // 引入打包的第三方组件
                        new webpack.DllReferencePlugin({
                        manifest: require('../src/dll/ui-manifest.json')
                        }),
          
                        new webpack.DllReferencePlugin({
                        manifest: require('../src/dll/vue-manifest.json')
                        }),
                    }
                }
          
      • UglifyJsPlugin(参数parallel:true 代表并行的进行压缩和混淆和cache)
      • HappyPack(并行的处理loader, HappyPack.ThreadPool共享线程)
      • babel-loader(开启缓存:options.cacheDirectory;规定打包范围:include/exclude)
      • 其他(减少resolve/Devtool:去除sourcemap/cache-loader/升级node和webpack)
      • 总结原则
        • 并行处理多个任务UglifyJsPlugin/HappyPack
        • 减少打包任务和限定范围(DllPlugin/DllReferencePlugin/include/exclude)
        • 利用缓存
        • 跟进新版本
    • 长缓存优化
      • 用户访问url下载请求,服务器通过header头cache设置缓存,以优化用户的访问速率
      • 场景:改变app代码, vendor未变化(如何打包后hash不变化以达到缓存的目的)
        • 提取vendor
        • hash -> chunkhash
      • 场景:引入新模块,模块顺序变化,vendor hash变化
              const path = require('path')
              const webpack = require('webpack')
        
              module.exports = {
                  entry: {
                      main: './src/foo',
                      // 分开业务代码和vendor代码
                      vendor: ['react']
                  },
        
                  output: {
                      path: path.resolve(__dirname, 'dist'),
                      // 分离vendor代码后, 使用chunkhash取代hash
                      filename: '[name].[chunkhash].js'
                  },
        
                  plugins: [
                      // 解决场景:引入新模块,模块顺序变化,vendor hash变化
                      // 原理: 给chunk设置name不因顺序而改变hash
                      new webpack.NamedChunksPlugin(),
                      new webpack.NamedModulesPlugin(),
                      // 提取公共代码
                      new webpack.optimize.CommonsChunkPlugin({
                          name: 'vendor',
                          minChunks: Infinity
                      }),
                      // 提取 runtime
                      new webpack.optimize.CommonsChunkPlugin({
                          name: 'manifest'
                      })
                  ]
              }
        
        
      • 总结
        • 完全独立打包vendor
        • 抽出manifest(webpack runtime)
        • 分配确定的id,解决顺序变化导致的hash变化:webpack.NamedChunksPlugin(),webpack.NamedModulesPlugin()
        • 动态模块给定模块名称
                // 模块动态加载
                import(/* webpackChunkName: 'async' */'./async').then(function (a) {
                    console.log(a + '11')
                })
          
  10. webpack多页面应用
    • 特点
      • 多入口entry
      • 多页面html
      • 每个页面不同的chunk
      • 每个页面的参数
    • 多配置和单配置
      • 多配置优缺点
        • 可以使用parallel-webpack来提高打包的速度 安装后在node_modules/webpack/parallel-webpack/bin/run.js执行 ` parallel-webpack –watch ` ` parallel-webpack –config`
        • 配置更加独立、灵活
        • 不能多页面之间共享代码
                template
                <!DOCTYPE html>
                <html>
                <head>
                    <meta charset="utf-8" />
                    <meta http-equiv="X-UA-Compatible" content="IE=edge">
                    <!-- 多页面模板 ejs语法-->
                    <title><%= htmlWebpackPlugin.options.title %></title>
                    <meta name="viewport" content="width=device-width, initial-scale=1">
                </head>
                <body>
                                  
                </body>
                </html>
          
                // npm install webpack-merge(合成config) webpack(优化插件) html-webpack-plugin(生成html页面) clean-webpack-plugin(重新生成打包目录) extract-text-webpack-plugin(提取css)
                // 多配置
                // 合并config
                const merge = require('webpack-merge')
                const webpack = require('webpack')
                // 生成html
                const HtmlWebpackPlugin = require('html-webpack-plugin')
                // 清除打包文件
                const CleanWebpack = require('clean-webpack-plugin')
                // css提取
                const ExtractTextWebpack = require('extract-text-webpack-plugin')
                const path = require('path')
                // 基本配置
                const baseConfig = {
                    entry: {
                        // 单独对react打包,react全家桶也一样
                        react: ['react']
                    },
          
                    output: {
                        // 绝对路径配置
                        path: path.resolve(__dirname, 'dist'),
                        filename: 'js/[name].[chunkhash].js'
                    },
          
                    module: {
                        rules: [
                            // 处理css
                            {
                                test: /\.css$/,
                                use: ExtractTextWebpack.extract({
                                    fallback: 'style-loader',
                                    use: 'css-loader'
                                })
                            }
                        ]
                    },
          
                    plugins: [
                        // 提取css
                        new ExtractTextWebpack({
                            filename: 'css/[name].[hash].css'
                        }),
                        new CleanWebpack(path.resolve(__dirname, 'dist')),
                        // 提取公共代码
                        new webpack.optimize.CommonsChunkPlugin({
                            name: 'react',
                            // 只有react
                            minChunks: Infinity
                        })
                    ]
                }
          
                // 以下采用了ES6默认对象的写法
                // 定义一个生成每个page config的函数
                const generatePage = function ({
                    title = '',
                    entry = '',
                    template = './src/index.html',
                    name = '',
                    chunks = []
                } = {}) {
                    return {
                        entry,
                        plugins: [
                            new HtmlWebpackPlugin({
                                chunks,
                                template,
                                filename: name + '.html'
                            })
                        ]
                    }
                }
                // 生成pages的config数组
                const pages = [
                    // 每个页面都需要加载react chunk
                    generatePage({
                        title: 'page A',
                        entry: {
                            a: './src/pages/a'
                        },
                        name: 'a',
                        chunks: ['react', 'a']
                    }),
                    generatePage({
                        title: 'page B',
                        entry: {
                            b: './src/pages/b'
                        },
                        name: 'b',
                        chunks: ['react', 'b']
                    }),
                    generatePage({
                        title: 'page C',
                        entry: {
                            c: './src/pages/c'
                        },
                        name: 'c',
                        chunks: ['react', 'c']
                    })
                ]
          
                // console.log(pages.map(page => merge(baseConfig, page)))
                // merge config并exports 一个数组
                module.exports = pages.map(page => merge(baseConfig, page))
          
          
      • 单配置优缺点
        • 可以共享各个entry之间的公用代码
        • 打包速度慢
        • 输出的内容比较复杂
                // 同多配置只是最后处理方式不同
                // merge config并exports 一个数组
                console.log(merge([baseConfig].concat(pages)))
                // 通过concat合成一个config
                module.exports = merge([baseConfig].concat(pages))
          

          webpack在框架中的应用

  11. vue-cli:vue2.x + webpack
    • 脚手架

      npm install vue-cli -g vue list 查看相关命令 vue init webpack my-vue-project

    • 项目结构(在代码中梳理)
    • 配置文件(在代码中梳理)
  12. create-react-app: react + webpack
    • 脚手架
      • react-scripts

      npm install create-react-app -g npx create-react-app my-project(npm 5.2+) create-react-app my-project(npm 5.2-)

      npm start npm test npm run build npm install -g serve serve -s build npm run eject

    • 项目结构(在代码中梳理)
    • 功能
      • 支持ES6和JSX(内部配置了babel)
      • 支持动态import
      • 支持Fetch(通过polyfill垫片)
      • 支持proxy
      • 支持postcss
      • 支持eslint
      • 支持单元测试
      • 不支持React Hot-reloading
      • 弱支持CSS预处理器
    • 自定义配置
      • proxy配置
              // 在package.json增加配置
              "proxy": {
                  "/api": {
                      "target":"https://proxy.test.com",
                      "changeOrigin": true
                  }
              }
        
              //单独配置setupProxy.js
              const proxy = require('http-proxy-middleware');
        
              module.exports = function(app) {
                  // ...You can now register proxies as you wish!
                  app.use(proxy('/api', {
                      target: 'http://172.19.5.35:9536',
                      secure: false,
                      changeOrigin: true,
                      pathRewrite: {
                      "^/api": "/"
                      },
                  }));
                  app.use(proxy('/apc', {
                      target: 'http://172.19.5.34:9531',
                      secure: false,
                      changeOrigin: true,
                      pathRewrite: {
                      "^/apc": "/"
                      },
                  }));
                  //app.use(proxy('/apc', { target: 'http://172.19.5.34:9531' }));
              };
        
      • Less配置
        • 添加对应loader和配置即可,在postcss之前处理。
      • React 热更新配置
        • react-hot-loader(在webpack dev中配置和index.js根节点引用)
                // index.js
                import { AppContainer } from 'react-hot-loader'
                import React from 'react';
                import ReactDOM from 'react-dom';
                import './index.css';
                import './css/base.less';
                import * as serviceWorker from './serviceWorker';
                import App from './components/App';
          
                const render = Component => {
                ReactDOM.render(
                    <AppContainer>
                    <Component />
                    </AppContainer>,
                    document.getElementById('root')
                )
                }
          
                // ReactDOM.render(<Index />, document.getElementById('root'));
                render(App)
          
                // If you want your app to work offline and load faster, you can change
                // unregister() to register() below. Note this comes with some pitfalls.
                // Learn more about service workers: https://bit.ly/CRA-PWA
                serviceWorker.unregister();
          
                if (module.hot) {
                    module.hot.accept('./components/App', () => {
                        render(App)
                    })
                }
          
                // webpack.config.js
                entry: [
                    // 其他配置
                    'react-hot-loader/patch', //设置模块热更新
                ],
                module: {
                    rules: [
                        {
                            oneof: [
                                // "url" loader works like "file" loader except that it embeds assets
                                // smaller than specified limit in bytes as data URLs to avoid requests.
                                // A missing `test` is equivalent to a match.
                                {
                                test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
                                loader: require.resolve('url-loader'),
                                options: {
                                    limit: 10000,
                                    name: 'static/media/[name].[hash:8].[ext]',
                                },
                                },
                                // Process application JS with Babel.
                                // The preset includes JSX, Flow, TypeScript, and some ESnext features.
                                {
                                test: /\.(js|mjs|jsx|ts|tsx)$/,
                                include: paths.appSrc,
                                loader: require.resolve('babel-loader'),
                                options: {
                                    customize: require.resolve(
                                    'babel-preset-react-app/webpack-overrides'
                                    ),
          
                                    plugins: [
                                    [
                                        require.resolve('babel-plugin-named-asset-import'),
                                        {
                                        loaderMap: {
                                            svg: {
                                                ReactComponent: '@svgr/webpack?-svgo,+ref![path]',
                                            },
                                        },
                                        },
                                    ],
                                    // 模块热更新
                                    'react-hot-loader/babel'
                                    ],
                                    // This is a feature of `babel-loader` for webpack (not Babel itself).
                                    // It enables caching results in ./node_modules/.cache/babel-loader/
                                    // directory for faster rebuilds.
                                    cacheDirectory: true,
                                    cacheCompression: isEnvProduction,
                                    compact: isEnvProduction,
                                },
                            ]
                        }
                    ]
                }
          
  13. angular-cli
    • 脚手架 npm install -g @angular/cli ng new ng-project --style=less --directory =src ng g/generate ng serve ng build ng test ng e2e ng lint ng eject`
    • ng serve(启动Webpack Dev Server)
      • 编译Less/Sass
      • 编译Typescript
      • 打包JS/CSS
      • 热更新
      • 代码分割
      • 接口代理
      • 接口代理

        ng serve --proxy-config proxy.conf.json

    • 第三方依赖安装

      npm install lodash --save npm install @type/lodash --save-dev

webpack总结

  1. 什么是webpack?它和grunt,gulp有什么不同?

    在webpack里一切文件皆模块,通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合成的文件,webpack专注构建模块化项目。webpack可以堪称是一个模块打包器,它可以递归的打包项目中的所有的模块,最终生成几个打包后的文件。它和其他工具最大的不同是它支持(code-splitting )代码分割、模块化(AMD、ESM、CommonJs),全局分析。 与grunt、gulp的不同

    • Webpack通过分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案.

    • 他们的工作方式也有较大区别:

      • Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。

      • Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

  2. 什么是bundle、chunk、module?

    bundle是webpack打包出来的文件,chunk是webpack在进行模块的依赖分析的时候,代码分割出来的代码块。module是开发中的单个模块。

  3. 什么是loader?什么是Plugin?

    loader是用来告诉webpack如何转化处理某一种类型的文件,并且引入到打包出的文件中。 Plugin是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期),相当于钩子函数。

  4. webpack-dev-server和http服务器如nginx有什么不同?

    webpack-dev-server使用内存来储存webpack开发环境下的打包文件,并且可以使用模块进行热更新。它比传统的http服务开发更加简单高效。

  5. 什么是模块热更新?

    模块热更新是webpack的一个功能,它可以使代码修改后不用刷新浏览器就可以更新。它的实现原理是websocket推送变更消息,监听到变化,代码重新执行一遍,修改数据。

  6. webpack的工作原理?

    webpack 的工作原理,如果不涉及到其他的插件其实从webpack 打包出来的源码中就能看出,或者是他产出的profile,webpack 会把打包后的文件都编号,然后再通过内置的方法require,表面上是这样,其实其内部还涉及到了一系列的依赖分析,其中涉及到抽象语法树(Abstract Syntax Tree) 等等一系列的行为。

  7. 什么是长缓存?在webpack中如何做到长缓存?

    浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行储存,但是每一次代码升级或更新都需要浏览器去下载新代码。最方便和简单的方式就是引入新的文件名称。在webpack中可以在output给输出的文件chunkhash,并且分离经常更新的代码和框架的代码。通过NamedModulesPlugin或HashedModuleIdsPlugin使再次打包的文件名不变。

  8. 什么是Tree-shaking? CSS可以Tree-shaking?

    Tree-shaking是指在打包中去除那些引入了,但是在代码中没有被用到的那些死代码。在webpack中Tree-shaking是通过uglifgJSPlugin来Tree-shaking。JS. CSS需要使用Plfify-CSS.

  9. Webpack工程化思想

    一切皆模块、急速的调试响应速度、优化应该自动完成。零配置、更快、更小、优化应该自动完成。

  10. webpack动态import原理

    import()把模块的名字作为一个参数,并且返回一个Promise: import(name)->Promise.通过函数式的引入模块,import()函数会返回一个promise,做到动态/异步的去加载模块。

    ```js
        function determineDate() {
            import('moment').then(function(moment) {
                console.log(moment().format());
            }).catch(function(err) {
                console.log('Failed to load moment', err);
            });
        }
    
        determineDate();
    ```
    ```js
        // 下面2行代码,没有指定webpackChunkName,每个组件打包成一个js文件。
        const ImportFuncDemo1 = () => import('../components/ImportFuncDemo1')
        const ImportFuncDemo2 = () => import('../components/ImportFuncDemo2')
        // 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。
        // const ImportFuncDemo = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo')
        // const ImportFuncDemo2 = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo2')
        export default new Router({
            routes: [
                {
                    path: '/importfuncdemo1',
                    name: 'ImportFuncDemo1',
                    component: ImportFuncDemo1
                },
                {
                    path: '/importfuncdemo2',
                    name: 'ImportFuncDemo2',
                    component: ImportFuncDemo2
                }
            ]
        })
    ```
    
  11. webpack代码分割和按需加载
    • CommonsChunkPlugin插件
    • entry和output配置提取库代码和公共代码
    • 提取Runtime(运行时)代码(manifest,以保证chunkhash不变)
    • 使用DllPlugin和DllReferencePlugin分割代码(dll包的代码是不会执行的,需要在业务代码中通过require显示引入。)
    • import()和require.ensure() -> 动态加载代码
  12. webpack和gulp区别(模块化与流的区别)

gulp强调的是前端开发的工作流程,我们可以通过配置一系列的task,定义task处理的事务(例如文件压缩合并、雪碧图、启动server、版本控制等),然后定义执行顺序,来让gulp执行这些task,从而构建项目的整个前端开发流程。

    // gulp的使用

    npm install gulp-cli -g
    npm install gulp -D
    npx -p touch nodetouch gulpfile.js
    gulp --help

gulpfile.js是gulp项目的配置文件,是位于项目根目录的普通js文件(其实将gulpfile.js放入其他文件夹下亦可)。

    //导入工具包 require('node_modules里对应模块')
    var gulp = require('gulp'), //本地安装gulp所用到的地方
        less = require('gulp-less');
    //定义一个testLess任务(自定义任务名称)
    gulp.task('testLess', function () {
        gulp.src('src/less/index.less') //该任务针对的文件
            .pipe(less()) //该任务调用的模块
            .pipe(gulp.dest('src/css')); //将会在src/css下生成index.css
    });
     
    gulp.task('default',['testLess', 'elseTask']); //定义默认任务
     
    //gulp.task(name[, deps], fn) 定义任务  name:任务名称 deps:依赖任务名称 fn:回调函数
    //gulp.src(globs[, options]) 执行任务处理的文件  globs:处理的文件路径(字符串或者字符串数组)
    //gulp.dest(path[, options]) 处理完后文件生成路径

上述例子运行gulp testLess即可执行gulp流任务进行css处理。官方网站

webpack是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。