webpack知识点

2020-06-19  本文已影响0人  noyanse

安装

// 每次更新都要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

配置入口

module.exports = {
  context: path.join(__dirname, './src/scripts'), // 默认工程根目录
  entry: './index.js
}
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'
}

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内容无关,需要由开发者手动绑定
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
// 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
// 假设当前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
// 假设当前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
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
}
上一篇下一篇

猜你喜欢

热点阅读