美文网首页
webpack learning note

webpack learning note

作者: AmazRan | 来源:发表于2018-09-03 18:36 被阅读24次

    核心概念

    入口(entry)、输出(output)、loader、插件(plugins)


    配置

    • entry 指定入口文件,默认值为./src。要多个依赖文件一起注入使用数组
    • output 指定出口文件,告诉 webpack 在哪里输出它所创建的bundles,以及如何命名这些文件,默认值为./dist。webpack可以存在多个入口起点,但只指定一个输出配置
    • mode 选择development/production告诉webpack选用相应模式的内置优化。只设置NODE_ENV,则不会自动设置mode
    • module 制定规则匹配处理
      rule规则可以分为三部分 - 条件(condition),结果(result)和嵌套规则(nested rule)
      条件有两种输入值:一、 resource:请求文件的绝对路径。二、issuer: 被请求资源的模块文件的绝对路径。是导入时的位置。
      例如:从 app.js 导入 './style.css',resource 是 /path/to/style.css. issuer 是 /path/to/app.js
    • resolve 解析
      alias 配置目录别名,创建 import 或 require 的别名,来确保模块引入变得更简单。
      extensions 自动解析确定的扩展,让用户引入模块时不带扩展。
      resolveLoader 这组选项与上面的 resolve 对象的属性集合相同,但仅用于解析 webpack 的 loader 包
    • plugins 插件 传入new实例,常用插件举例:
      代码压缩插件 UglifyJsPlugin 已弃用,生产模式会自动压缩
      图片压缩插件 imagemin-webpack-plugin
      HTML文件创建插件 HtmlWebpackPlugin 用于创建html,自动重新生成一个index.html,已帮你把生产的所有js文件引入,生成到output目录
      清空文件夹插件 clean-webpack-plugin
    resolveLoader: {
        modules: [ 'node_modules' ],
        extensions: [ '.js', '.json' ],
        mainFields: [ 'loader', 'main' ]
      }
    

    范例

    // 养成良好习惯,将可配置项路径、开关等收纳于config文件,
    // 尽可能减少后期webpack.config文件的修改
    module.exports = {
      // 入口文件的配置项
      entry: utils.getEntries(['./src/*.js', './src/pages/*.js']), 
      // 出口文件的配置项
      output: {
        path: config.path, //打包的文件路径
        filename: '[name].js',  //打包的文件名称,[name]为模块名称
        publicPath: config.publicPath //指定在浏览器中所引用的「此输出目录对应的公开 URL」
      },
      resolve: {
        extensions: ['.js', '.vue', '.json'], //将自动解析.js/vue/json扩展名
        alias: {
          '~': resolve('node_modules'), // 创建引入的别名方便书写
        }
      },
      module: {
        rules: [
        // 创建模块时,匹配请求的规则数组。这些规则能够修改模块的创建方式。
        // 这些规则能够对模块(module)应用 loader,或者修改解析器(parser)。
          {
            test: /\.(js|vue)$/, //引入的匹配规则,对.js/vue文件生效
            loader: 'eslint-loader', //Rule.use: [ { loader } ]的简写
            enforce: 'pre', //loader种类,pre前置类型,post后置类型,默认普通
            include: [resolve('src'), resolve('test')], //匹配src/test文件夹内
            exclude:[resolve('dist')], //排除dist文件夹
            options: { //loader选项,值可以传递到 loader 中
              formatter: require('eslint-friendly-formatter')
            }
          },
          {
            test: /\.vue$/,
            loader: 'vue-loader',
            options: vueLoaderConfig //传配置好的config对象
          },
          {
            test: /\.js$/,
            loader: 'babel-loader',
            include: [resolve('src'), resolve('test')],
            query: { //等同于options
            //Rule.options 和 query 是 Rule.use: [ { options } ] 的简写
              cacheDirectory: true
            }
          }
        ]
      },
      optimization: {
        splitChunks: {
        //提取被重复引入的文件,单独生成一个或多个文件,这样避免在多入口重复打包文件
          cacheGroups: { //自定义配置
            shared: {
              name: 'shared', //被打包到shared.js
              chunks: 'initial',
              minChunks: 2
            },
            vendor: { // split `node_modules`目录下被打包的代码到 `page/vendor.js && .css` 没找到可打包文件的话,则没有。css需要依赖 `ExtractTextPlugin`
              test: /node_modules\//, //split `node_modules`目录
              name: 'page/vendor', //被打包的代码到'page/vendor.js&&.css'
              priority: 10, //缓存优先级
              enforce: true
            },
          }
        }
      },
      plugins:[] //引入插件
    }
    

    范例中的要点

    1. path是webpack所有文件的输出的路径,必须是绝对路径。比如:output输出的js,url-loader解析的图片,HtmlWebpackPlugin生成的html文件,都会存放在以path为基础的目录下
    2. publicPath 并不会对生成文件的路径造成影响,主要是对你的页面里面引入的资源的路径做对应的补全,常见的就是css文件里面引入的图片
    3. splitChunk的特点:
      不会下载不需要的代码
      对异步chunks也很高效,被默认用于异步chunks
      可以通过多个vendor chunks来进行vender的分割
      使用简单,不依赖块图,基本上是自动的
      原理就是把多个入口共同的依赖都给定义成一个新入口

    小于30kb的模块不值得再单独发送一次请求,在很小的模块的前提下,相比与多次打包,减少请求次数成本要低。

    1. webpack是如何进行资源的打包的呢?
      每个文件都是一个资源,可以用require导入js
      每个入口文件会把自己所依赖(即require)的资源全部打包在一起,一个资源多次引用的话,只会打包一份
      对于多个入口的情况,其实就是分别独立的执行单个入口情况,每个入口文件不相干
    2. webpack打包的原理为,在入口文件中,对每个require资源文件进行配置一个id。因此对于同一个资源,就算是require多次它id一样,所以无论在多少个文件中 require,它都只会打包一分。

    关于按需加载

    webpack 在编译时会静态地解析代码中的 require.ensure(),同时将模块添加到一个分开的 chunk 当中。这个新的 chunk 会被 webpack 通过 jsonp 来按需加载。
    require.ensure其实就是把js模块给独立导出一个.js文件的,然后使用这个模块的时候,webpack会构造script dom元素,由浏览器发起异步请求这个js文件。
    语法:require.ensure(dependencies: String[], callback: function(require), chunkName: String)

    注:webpack会把参数里面的依赖异步模块和当前的需要分离出去的异步模块给一起打包成同一个js文件,这里可能会出现一个重复打包的问题, 假设A和B都是异步的, ensure A中依赖B,ensure B中 依赖A,那么会生成两个文件,都包含A和B模块。

    基础:require(request) request指在 require() 语句中的表达式,
    "./template/" + name + ".ejs",生成一个具有上下文的模块。它包含目录下的所有模块的引用,这些模块能够「通过从 request 匹配出来的正则表达式」所 require 进来。上下文模块包含一个 map 对象,会把 request 中所有模块转译成对应的模块 id。


    关于 Babel

    Babel通常与webpack搭配一起使用,webpack可以看作是是打包工具,babel则视为编译工具,可把最新标准编写的js代码(例如 es6,react..)编译成当下随处可用的版本。是一种“源码到源码”的编译(转换编译)
    通常我们用到的有:babel-loader, babel-core, babel-preset-env。其中,babel-preset-env插件是为了告诉babel只编译批准的内容,相当于babel-preset-es2015, es2016, es2017及最新版本。

    如果我们使用一些特性或方法,比如Generator, Set, 或者一些方法。babel并不能转换为低版本浏览器识别的代码(babel只做语法的转义)。这时就需要babel-polyfill垫片,提供了一些低版本es标准对高级特性的实现。

    比较babel-polyfill与transform-runtime
    Babel转换代码中额外用到了很多小的方法,而且每一个文件都有。这些方法是重复的,transform-runtime的其中一个作用就是把这些方法抽出来,重复利用。
    那么还有个重要作用是polyfill。插件transform-runtime里的polyfill,和插件babel-polyfill的polyfill的不同点在于其不会污染全局变量。
    凡事都有例外,像"foobar".includes("foo") 的实例方法还是需要用插件babel-polyfill。因为那种实例方法会修改原有的built-ins( 例如Promise, Set and Map)
    总结一下就是runtime的优点:1.不会污染全局变量 2.多次使用只会打包一次 3.依赖统一按需引入,无重复引入,无多余引入
    缺点:1.不支持实例化的方法Array.includes(x) 就不能转化 2.如果使用的API用的次数不是很多,那么transform-runtime 引入polyfill的包会比不是transform-runtime 时大
    :需要配合webpack/browserify,浏览器不兼容CommonJS


    webpack-dev-server

    1. webpack输出真实的文件,而webpack-dev-server输出的文件只存在于内存中,不输出真实的文件!
    2. 关于模块热替换
      从外部角度看 自动刷新。当我们对业务代码做了一些修改然后保存后(command+s),页面会自动刷新,我们所做的修改会直接同步到页面上,而不需要我们刷新页面,或重新开启服务
      从内部角度看 模块热替换。在热替换(HMR)机制里,不是重载整个页面,HMR程序会只加载被更新的那一部分模块,然后将其注入到运行中的APP中
    3. webpack-dev-server有两种模式可以实现自动刷新和模块热替换机制:
      Iframe mode(默认,无需配置)页面被嵌入在一个iframe里面,并且在模块变化的时候重载页面。每次修改后都是这个 iframe 进行了 reload。
      inline mode(需配置)添加到bundle.js中
      当刷新页面的时候,一个小型的客户端被添加到webpack.config.js的入口文件中,从而完成了将新内容打包进bundle.js中。
    4. webpack-hot-middleware和webpack-dev-middleware一般使用这两个插件实现热加载。本地启动express服务再将两者挂载上去,开启端口监听。
    // vue-cli中生成的范例,单独拎出来是因为实际配置不需要这么详细
    // 实际参考上方要点4
    devServer: {
      clientLogLevel: 'warning',
      historyApiFallback: { //应对返回404响应指向 index.html 页面
        rewrites: [
          { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
        ],
      },
      hot: true, //启动webpack热模块替换特性
      contentBase: false, //指定服务器资源根目录
      // vue-cli使用了CopyWebpackPlugin.因此配置为false
      compress: true, //对所有的服务器资源采用gzip压缩
      //优点:极大提高传输速率;缺点:需要压缩解压,增加服务端客户端负载
      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, // true时控制台只输出第一次编译的信息,保存后再次编译不会输出任何内容。
      // vue-cli为了更好的错误提示使用了FriendlyErrorsPlugin,所以关闭了初始提示。
      watchOptions: {
        poll: config.dev.poll,
      }
    }
    

    html-webpack-plugin

    new htmlWebpackPlugin({
        title: 'demo', //生成html文件的标题
        filename: 'index.html', //html文件名
        template: 'index.html', //指定生成的文件依赖的html模板
        inject: 'head', //script标签注入到头部,true默认为'body'底部
        chunks: ['index','main'] //主要用于多入口文件。
        //当你有多个入口文件生成多个打包后的文件,可以通过chunks选择要使用的js文件
    })
    

    平时开发的小tips

    1. path 对于以/开始的路径片段,path.join只是简单的将该路径片段进行拼接,而path.resolve将以/开始的路径片段作为根目录,在此之前的路径将会被丢弃,就像是在terminal中使用cd命令一样。
    const path = require('path');
    let myPath = path.join(__dirname,'/img/so');
    let myPath2 = path.join(__dirname,'./img/so');
    let myPath3 = path.resolve(__dirname,'/img/so');
    let myPath4 = path.resolve(__dirname,'./img/so');
    console.log(__dirname); //D:\myProgram\test
    console.log(myPath); //D:\myProgram\test\img\so 
    console.log(myPath2); //D:\myProgram\test\img\so 
    console.log(myPath3); //D:\img\so
    console.log(myPath4); //D:\myProgram\test\img\so
    
    1. Node.js 中,__dirname 总是指向被执行 js 文件的绝对路径,所以当你在 /d1/d2/myscript.js 文件中写了 __dirname, 它的值就是 /d1/d2 。
    2. 使用opn打开文件、url
    const opn = require('opn');
    
    // opens the image in the default image viewer 
    opn('unicorn.png').then(() => {
        // image viewer closed 
    });
    
    // opens the url in the default browser 
    opn('http://jianshu.com');
    
    // specify the app to open in 
    opn('http://jianshu.com', {app: 'firefox'});
    
    // specify app arguments 
    opn('http://jianshu.com', {app: ['google chrome', '--incognito']});
    
    1. process.env属性返回一个包含用户环境信息的对象。
      平时用作区分dev/生产环境。
    2. fs模块常用API
    const fs = require('fs')
    const path = require('path')
    var fsPath = path.join(__dirname,'./1.txt')
    
    fs.stat(fsPath, (err,stats)=>{})
    // 可以异步检测文件状态。err错误信息,stats状态
    
    fs.statSync(fsPath)
    // 同步版,只接受一个path参数。stat配合isDirectory()校验是否存在
    
    fs.writeFile(fsPath, data[,options], callback)
    // 写入内容覆盖原内容,不存在就先创建
    
    fs.appendFile(fsPath, data[,options], callback)
    // 写入内容追加在原内容后,不存在就先创建
    
    fs.unlink(fsPath, (err)=>{})
    // 删除文件。callback只有一个err参数返回错误信息
    
    fs.readFile(fsPath[, options], callback)
    // 读取文件,默认编码格式为Buffer,一般配置'utf8'
    
    fs.rename(oldPath,newPath,callback)
    // 重命名/移动文件
    
    fs.mkdir(path[,model],callback)
    // 创建文件夹
    
    fs.readdirSync(__dirname).forEach(file => {})
    // 返回一个包含“指定目录下所有文件名称”的数组对象,一般后续接循环对file作处理
    

    总结

    webpack以及涉及到的nodejs的一些基本用法已经大致列举。
    其他内容后续会再细节展开。


    文档

    webpack官方文档
    webpack4.x入门配置
    webpack实战
    path的join和resolve
    node express使用介绍
    Express 结合 Webpack 实现HMR

    相关文章

      网友评论

          本文标题:webpack learning note

          本文链接:https://www.haomeiwen.com/subject/ygxriftx.html