美文网首页
webpack知识点

webpack知识点

作者: noyanse | 来源:发表于2020-06-19 16:08 被阅读0次

    安装

    // 每次更新都要npm run build
    1. npm install webpack webpack-cli --save-dev
    // 只有本地开发会用到
    2. npm install webpack-dev-server --save-dev
    

    package.json:

    'scripts': {
      'build': 'webpack',
      'dev': 'webpack-dev-server
    }
    

    在webpack.config.js中配置devServer

    wepack-dev-server主要职责:

    1. 令webpack进行模块打包,并处理打包结果的资源请求
    2. 作为普通的we server,处理静态资源文件请求
      wepack-dev-server只是将结果写在内存里,每次接受到请求从内存读取,并不会生成bundle.js
    // 专门放webpack-dev-server的配置
    module.exports = {
      entry: './index.js',
      output: {
        filename: 'bundle.js'
      },
      mode: 'development',
      devServer: {
        publicPath: '/dist'
      }
    }
    // 执行npm run dev
    

    配置入口

    • context 在多入口的情况下,使entry更简洁,可以省略
    module.exports = {
      context: path.join(__dirname, './src/scripts'), // 默认工程根目录
      entry: './index.js
    }
    
    • entry 可以字符串,数组,对象,函数
      1.字符串
    module.exports = {
      entry: './src/index.js,
      output: {
        filename: 'bundle.js
      },
      mode: 'development'
    }
    

    2.数组

      entry: ['babel-polyfill': './src/index.js'],
      相当于
      // webpack.config.js:
      entry: './src/index.js'
      // index.js
      import 'babel-polyfill'
    

    3.对象:如果使用多入口,必须使用对象

    entry: {
      index: './src/index.js', // chunk name为index 入口路径就是./src/index.js
      lib: './src/lib.js'
    }
    
    对象的属性值也可以为字符串或数组
    entry: {
      index: ['babel-polyfill': './src/index.js'],
      lib: './src/lib.js'
    }
    
    • 在使用字符串或数组定义单入口时,没法改变chunk name,只能时默认的main
      使用多入口时,必须为每个入口定义chunk name

    4.函数: 只要返回上面的任何配置形式就行

    entry: () => './src/index.js'
    

    传入函数的优点:可以在函数体内添加动态的逻辑来获取工程的入口
    函数也支持返回一个Promise对象来进行异步操作

    // 模拟异步
    entry: () => new Promise(resolve => {
      setTimeout(() => {
        resolve('./src/index.js')
      }, 1000)
    })
    

    vendor 供应商,一般指工程中所使用的库,框架等第三方模块集中打包产生的bundle

    module.exports = {
      context: path.join(__dirname, './src'),
      entry: {
        app: './src/app.js',
        // 添加新的chunk name为 vendor的入口,通过数组形式把工程依赖的第三方模块放进去
        vendor: ['react', 'react-dom', 'react-router']
      }
    }
    

    使用CommonsChunkPlugin(webpack4之后已被废弃,可用optimization.splitChunks)
    将app 和 vendor 这两个 chunk 中的公共模块提取出来
    第三方模块会被抽成新的bundle,利用客户端缓存,在后续请求页面时,加快渲染速度

    多页应用

    每个页面都有一个独立的bundle

    entry: {
      pageA: './src/pageA.js',
      pageB: './src/pageB.js',
      pageC: './src/pageC.js',
    }
    

    入口和页面是 1对1的关系,这样每个HTML只要引入各自的js就可以家在所需要的模块
    也可提取vendor的方法,将公共模块打包

    entry: {
      pageA: './src/pageA.js',
      pageB: './src/pageB.js',
      pageC: './src/pageC.js',
      vendor: ['react', 'react-dom']
    }
    

    配置出口

    1. filename
      控制输出文件的名称,也可以是一个相对路径,如果没有该路径会创建 ./js/bundle.js
    const path = require('path')
    module.exports = {
      entry: './src/app.js',
      output: {
        filename: 'bundle.js',
    
        path.join(__dirname, 'assets'),
        pablicPath: '/dist/'
      }
    }
    

    多入口时,可使用类似模版语言动态生成文件名

    entry: {
      app: './src/app.js',
      vendor: './src/vendor.js'
    },
    output: {
      filename: '[name].js'
    }
    

    在资源输出时,[name]会被替换成chunk name, 因此实际项目生成 vendor.js和 app.js
    其它模版变量:
    [hash] webpack此次打包所有资源生成的hash
    [chunkhash] 当前chunk内容的hash
    [id] 当前chunk的id
    [query] filename 配置项中的query
    以上变量作用:

    1. 当有多个chunk时,对不同chunk区分,如[name],[chunkhash],[id]对每个chunk都是不同的
    2. 控制客户端缓存,[hash],[chunkhash]与chunk内容直接相关,当chunk内容更改时,可以同时引起
      资源文件名的更改,下次请求文件会下载最新版本而不使用本地缓存
      [query]也起到类似效果,只不是它与chunk内容无关,需要由开发者手动绑定
    • 在实际中,使用多的是[name],它与chunk是一对一的关系,且可读性高,如果要控制客户端缓存最好
      加上[chunkhash],因为每个chunk所产生的[chunkhash]只与自身内容有关,单个chunk内容的改变
      不会影响其它资源,可以最精确的让客户端缓存得到更新
    module.exports = {
      entry: {
        app: './src/app/js',
        venfor: './src/vendor/js'
      },
      output: {
        filename: '[name]@[chunkhash].js'
      }
    }
    打包结果:
    vendor@0ddfasddfrfgrtg.js
    app@fksejrgorjgojh.js
    

    更新混存一般只用在生产环境的配置下,在开发环境中可以不必配置chunkhash

    1. path 指定资源输出的位置,要求值必须为绝对路径
    const path = require('path')
    module.exports = {
      entry: './src/app.js',
      output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
      }
    }
    

    webpack4之后,默认在dist目录,除非要更改,否则不必单独配置

    1. publicPath
    • path 用来指定资源的输出位置
      输出位置:打包完成后资源产生的目录,一般指定为工程中的dist目录
    • publicPath 指定资源的请求位置
      由js 或 css 所请求的简介资源路径,页面中的资源分为两种:
      1. 由html页面直接请求,如通过script标签家在的js
      2. 由js 或 css 请求的,如异步加载的js, 从 css 请求的图片字体等
        publicPath作用就是指定这部分间接资源的请求位置
    • 与html相关
      可将publicPath指定为html的相对路径
      在请求时,会以当前页面html所在路径加上相对路径,构成实际请求的url
    // html: https://example.com/app/index.html
    // 异步加载的资源: 0.chunk.js
    publicPath: '' // 实际路径为 https://example.com/app/0.chunk.js
    publicPath: './js' // 实际路径为 https://example.com/app/js/0.chunk.js
    publicPath: '../assets' // 实际路径为 https://example.com/assets/0.chunk.js
    
    • host相关
      publicPath值以/开始,则代表此时的publicPath是以当前页面的host name为基础路径的
    // 假设当前html地址为 https://example.com/app/index.html
    // 异步加载资源: 0.chunk.js
    publicPath: '/' // 实际路径:https://example.com/0.chunk.js
    publicPath: '/js/' // 实际路径:https://example.com/js/0.chunk.js
    publicPath: '/dist/' // 实际路径:https://example.com/dist/0.chunk.js
    
    • cdn相关
      当publicPath以协议头或相对协议的形式开始,代表当前路径是cdn相关
    // 假设当前html地址为 https://example.com/app/index.html
    // 异步加载资源: 0.chunk.js
    publicPath: 'http://cdn.com' // 实际路径:http://cdn.com/0.chunk.js
    publicPath: 'https://cdn.com' // 实际路径:https://cdn.com/0.chunk.js
    publicPath: '//cdn.com/assets/' // 实际路径://cdn.com/assets/0.chunk.js
    

    webpack-dev-server中也有个publicPath,与上面的那个不一样,指的是
    webpack-dev-server静态资源服务路径
    最好设置成一样的,保持开发环境和生产环境一样

    const path = require('path')
    module.exports = {
      entry: './src/app.js',
      output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
      },
      devServer: {
        publicPath: 'dist',
        port: 3000
      }
    }
    // localhost:3000/dist/bundle.js 可以得到预期结果
    

    实例

    1. 单入口
      不必设置动态的output.filename直接指定输出文件名即可
    const path = require('path')
    module.exports = {
      entry: './src/app.js',
      output: {
        filename: 'bundle.js'
      },
      devServer: {
        publicPath: '/dist' // webpack4以后output.publicPath默认为dist
      }
    }
    
    1. 多入口
      必须使用模版变量来配置filename
    module.exports = {
      entry: {
        pageA: './src/pageA.js',
        pageB: './src/pageB.js',
      },
      output: {
        filename: '[name].js'
      },
      devServer: {
        publicPath: '/dist/'
      }
    }
    // 最终生成:
    pageA.js
    pageB.js
    生成环境可以配置成[name]@[chunkhash].js
    

    预处理器

    1.webpack 一切皆模块的思想 与 loader概念

    1. loader原理
    2. 如何引入一个loader
    3. 常用loader介绍
    4. 如何便携一个loader

    一切皆模块

    html js css 模版 图片 字体等多种类型的静态资源,对webpack来说都是模块
    可以像加载js文件一样加载他们
    如:
    // index.js
    import './style.css
    这句引用的实际意义就是描述了js与css文件之间的依赖关系
    webpack本身只认识js, 其它类型必须预先定义一个或多个loader来转译成webpack能接受的形式

    loader 的引入

    npm install css-loader
    
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.css$/,
            use: ['css-loader']
          }
        ]
      }
    }
    // 此时再打包,就可以把.css文件打包进去了,但是页面上没有生效,因为css-loader仅仅处理了各种加载
    语法(@import 和 url())函数等,style-loader可以把样式插入页面,css-loader 和 style-loader
    通常是配合一起使用的
    

    test 可接受一个正则表达式或一个元素为正则表达式的数组,只有正则匹配上的模块才会使用这条规则
    use [] 包含该规则使用的loader

    链式loader

    npm install style-loader
    
    module.exports = {
      module: {
        rules: [{
          test: /\.css$/,
          use: ['style-loader', 'css-loader']
        }]
      }
    }
    

    webpack打包时是按照从后往前的顺序将资源给loader处理的,因此要把最后生效的放在最前面

    loader options

    rules: [{
      test: /\.css$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            // css-loader 配置项
          }
        }
      ]
    }]
    

    有些loader会用query来代替options,功能上一样

    更多配置

    1. exclude 与 include
      排除或包含指定目录下的模块,可接收正则表达式或字符串(文件绝对路径),以及由他们组成的数组
    rules: [{
      test: /\.css$/,
      use: ['style-loader','css-loader'],
      // 所有被正则匹配到的模块都排除在该规则之外,node_modules下的模块不会执行这条规则
      // 此配置项通常是必加的,否则会拖慢整体打包速度
      exclude: /node_modules/,
    }]
    

    通常用babel-loader来处理es6+,但是对于node_modules中的js文件来说,很多以及编译成es5了,没必要额外处理

    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
        // 该规则只对正则匹配到的模块生效
        include: /src/,
      }
    ]
    

    如果include 和 exclude 同时存在,exclude优先级更高

      test: /\.css$/,
      use: ['style-loader','css-loader'],
      exclude: /node_modules/,
      // 这个不起作用的哦
      // include: /node_modules\/awsome-ui/ 
      可以改成
      // 排除node_modules中除了foo和bar之外的所有模块
      exlude: /node_modules\/(?!(foo|bar)\/).*/
    

    由于exclude优先级更高,可对include中子目录进行排除

    exclude: /src/lib/,
    include: /src/
    

    resource 和 issuer

    用于更加精确的确定模块规则的作用范围
    在Webpack中,认为
    resource 是 被加载模块
    issuer 是 加载者
    如:index.js:

    import './style'
    

    resource是 /path/of/app/style.css
    iusser是 /path/of/app/index.js

    前面的test exclude include 是对resource被加载者的配置
    如果要对issuer加载者也增加条件限制,如让/src/pages下的js
    可以引用css

    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
        exclude: /node_modules/,
        issuer: { // 只有src/pages/下面的js文件引用css文件,这条规则才生效
          test: /\.js$/,
          include: /src/pages/,
        }
      }
    ]
    

    上面的可读性差 可 改成下面这种

    rules: [
      {
        use: ['style-loader', 'css-loader'],
        resource: {
          test: /\.css$/,
          exclude: /node_modules/,
        }
        issuer: {
          test: /\.js$/,
          include: /src/pages/,
        }
      }
    ]
    

    enforce 用来指定一个loader的种类,只接收 'pre' 'post' 两种字符串类型的值

    wepack中loader的执行顺序可分为pre inline normal post四种类型
    上面直接定义的loader都属于normal类型,inline形式官方已经不在推荐
    而 pre 和 post 需要enforce指定

    rules: [
      {
        test: /\.js$/,
        enforce: 'pre', // 代表在所有正常loader执行之前,保证代码不是被其它loader更改过的
        use: 'eslint-loader'
      }
    ]
    

    在所有loader之后执行,enforce设置为post

    事实上,可以不使用enforce,只要保证loader顺序是正确的即可,配置ecforce的目的:使模块规则更加清晰,可读性更强
    在实际项目中,配置文件可能达到上百行,难保哥哥loader都按照预想的方式执行, enforce可以强制指定loader的执行顺序

    常用loader

    1. babel-loader es6+ 编译为es5 使我们可以使用语言最新特性(甚至在提案中的),不必考虑不同平台的兼容问题
      npm install babel-loader @babel/core @babel/preset-env
    • babel-loader 使babel webpack协同国内工作的模块
    • @babel/core babel编译器的核心模块
    • @babel/preset-env 官方推荐的预置器,可根据用户设置的目标环境自动添加所需的插件和布丁来编译es6代码
    rules: [
      {
        test: /\.js$/,
        exclude: /node_module/, // 对所有js文件设置的规则,所以排除node_modules
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true, // 启动缓存机制,重复打包未改变过的模块时,防止二次编译
            presets: [[
              'env', 
              {
                // @babel/preset-env 会将es6 module转为common.js 会导致webpack的tree-shaking失效
                modules: false, // 禁用模块语句的转化,将es6 module的语法交给webpack处理
              }
            ]]
          }
        }
      }
    ]
    

    cacheDirectory 也可以接受一个字符串的路径来作为缓存路径, 设置为true时,缓存目录为:node_modules/.cache/babel-loader

    1. ts-loader 用于链接webpack与ts模块
      npm install ts-loader typescript
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      }
    ]
    

    ts本身的配置不在ts-loader中,在tsconfig.json中
    如:

    "compilerOptions": {
      "target": "es5",
      "sourceMap": true
    }
    
    1. html-loader
      用于将HTML文件转化为字符串并进行格式化,这样可以把HTML片段通过js加载进来
      npm install html-loader
    rules: [
      {
        test: /\.html$/,
        use: 'html-loader'
      }
    ]
    

    使用事例:
    header.html

    <header>
      <h1>this is a header</h1>
    </header>
    

    index.js

    import headerHtml from './header.html'
    document.write(headerHtml)
    

    header.html会转化为字符串,并通过document.write插入页面

    1. handlebars-loader
      用于处理handlebars模版,在安装时,要额外安装hanlebars
      npm install handlebars-loader handlebars
    rules: [
      {
        test: /\.handlebars$/,
        use: 'handlebars-loader'
      }
    ]
    
    1. file-loader
      打包文件类型的资源,并返回publicPath
      npm install file-loader
    const path = require('path')
    module.exports = {
      entry: './app.js',
      output: {
        path: path.join(__dirname, 'dist'),
        filename: 'bundle.js'
      },
      module: {
        rules: [
          {
            test: /\.png|jpg|gif&/,
            use: 'file-loader'
          }
        ]
      }
    }
    

    可以在js中加载图片了

    import avatarImage from './avatar.png'
    console.log(avatarImage) // gserygretyrsgert.png
    

    output.path是资源打包输出路径
    output.publicPath 是资源引用路径
    打包完后生成 gserygretyrsgert.png 的图片文件。由于默认配置中未指定output.publicPath
    这里打印的只是文件名,默认为文件的hash值加上文件后缀

    下面加上output.publicPath

    const path = require('path')
    module.exports = {
      entry: './app.js',
      output: {
        path: path.join(__dirname, 'dist'),
        filename: 'bundle.js',
        publicPath: './assets'
      },
      module: {
        rules: [
          {
            test: /\.(png|jpg|gif)$/
            use: 'file-loader'
          }
        ]
      }
    }
    

    此时的路径: ./assets/gserygretyrsgert.png

    file-loader可配置文件名以及publicPath 会覆盖output.publicPath

    rules: [
      {
        test: /\.(png|jpg|gif)$/
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            publicPath: './another-path'
          }
        }
      }
    ]
    

    此时的路径:./another-path/gserygretyrsgert.png

    1. url-loader
      与file-loader类似,唯一的不用是可以可以设置文件大小的阀值,当大于该阀值时与file-loader一样返回publicPath,
      小于该阀值时:返回文件base64形式编码
      npm install url-loader
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10240,
            name: '[name].[ext]',
            publicPath: './assets-path'
          }
        }
      }
    ]
    
    import avatarImage from './avatar.jpg'
    console.log(avatarImage)
    // data:image/jpeg;base64,/9j/....
    
    
    1. vue-loader
      处理vue组件,可将组件模版,js 样式进行拆分,安装时还要安装vue-template-compiler来编译vue模版
      以及css-loader来处理样式(如果是scss,less还需要对应的loader)
      npm install vue-loader vue vue-tamplate-complier css-loader
    rules: [
      {
        test: /\.vue$/,
        use: 'vue-loader' // 支持更多高级配置哦~
      }
    ]
    

    自定义loader

    1. loader初始化
    npm init -y
    touch index.js
    index.js:
    module.exports = function(content) {
      var userStrictPrefix = '\'use strict\';\n\n;
      return userStrictPrefix + content
    }
    
    npm install <path-to-loader>/force-strict-loader
    // 在webpack工程目录下使用相对路径安装,会在项目的mode_modules中创建一个指向实际force-strict-loader目录的软链,
    // 也就是说可以随时修改loader源码,不需要重新安装
    
    webpack.config.js:
    module: {
      rules: [
        {
          test: /\.js$/,
          use: 'force-strict-laoder
        }
      ]
    }
    
    1. this.cacheable启用缓存
      当文件输入和其依赖没有变化时,让loader直接使用缓存
      // force-strict-loader/index.js
    module.exports = function(content) {
      if (this.cacheable) {
        this.cacheable()
      }
      var userStrictPrefix = '\'use strict\';\n\n;
      return userStrictPrefix + content
    }
    
    1. 获取options
      loader的配置项通过options传进来
    module: {
      rules: [
        {
          test: /\.js$/,
          use: 'force-strict-laoder,
          options: {
            sourceMap: true
          }
        }
      ]
    }
    

    要在自己的loader中获取这个配置就要安装loader-utils, 主要提供一些帮助函数,在force-strict-loader目录下执行一下命令
    npm install loader-utils
    修改loader

    var loaderUtils = require('loader-utils')
    module.exports = function(content) {
      if (this.cacheable) {
        this.cacheable()
      }
      // 获取options配置对象
      var options = loaderUtils.getOptions(this) || {}
      console.log(options)
    
      var userStrictPrefix = '\'use strict\';\n\n;
      return userStrictPrefix + content
    }
    
    1. source-map
      便于开发者在浏览器控制台查看源码,如果没有source-map处理,最终无法生成正确的map文件 devtool中看到的就是乱的源码
      修改loader
    var loaderUtils = require('loader-utils')
    var SourceNode = require('source-map').SourceNode
    var SourceMapConsumer = require('source-map').SourceMapConsumer
    module.exports = function(content, sourceMap) {
      var userStrictPrefix = '\'use strict\';\n\n;
      if (this.cacheable) {
        this.cacheable()
      }
      // 获取options配置对象
      var options = loaderUtils.getOptions(this) || {}
      console.log(options)
    
      // source-map处理
      if(options.sourceMap && sourceMap) {
        var currentRequest = loaderUtils.getCurrentRequest(this)
        var node = SourceNode.fromStringWithSourceMap(
          content,
          new SourceMapConsumer(sourceMap)
        )
        node.prepend(userStrictPrefix)
        var result = node.toStringWithSourceMap({
          file: currentRequest
        })
        var callback = this.async()
        callback(null, result.code, result.map.toJSON())
      }
      // 不支持source-map情况
      return userStrictPrefix + content
    }
    

    相关文章

      网友评论

          本文标题:webpack知识点

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