一、webpack Loader的工作原理

实现一个markdown的文件加载器。
Loader作为webpack的核心机制,内部的工作原理也非常简单,我们通过开发一个自己的loader,来深入了解loader的工作原理。

需求:实现一个markdown文件的加载器,这个加载器可以在代码当中直接导入markdown文件。markdown文件一般是被转换为html过后再去呈现到页面上的,所以我们导入的markdown文件得到的结果就是转换过后的html字符串。
在项目的根目录下新建一个markdown-loader.js文件,每一个webpack的loader都需要去导出一个函数,这个函数是loader的对所加载到的资源的一个处理过程,它的输入就是加载到的资源文件的内容,输出就是加工过后的一个结果。

  1. module.exports = source => {
  2. console.log(source)
  3. return 'hello'
  4. }
  1. module: {
  2. rules: [
  3. {
  4. test: /.md$/,
  5. use: './markdown-loaderr' //use属性不仅可以写已经在webpack中的loader,还可以写目录下的
  6. }
  7. ]
  8. }

webpack的加载资源的过程类似于一个工作管道,可以在这个过程当中一次去使用多个loader,但是最终这个管道工作过后的结果必须是一段JavaScript代码
第1种办法:

  1. module.exports = source => {
  2. console.log(source)
  3. return 'console.log("hello")'
  4. }

打开bundle.js,webpack打包把我们刚刚loader加载过后的结果直接拼接到我们这个模块当中了,也就解释了loader的管道最后必须要返回JavaScript代码的原因,如果随便去返回一个内容的话,那放到这里语法就有可能不通过。

回到markdown-loader.js的当中,先去安装一个markdown解析的模块叫做marked,安装命令为:yarn add marked —dev

marked方法返回值是一段html字符串,接下来把这段html变成一段JavaScript代码,
通过JSON.stringify先将这个字符串转换为一个标准的JavaScript格式字符串,那此时内部的引号以及换行符都会被转义过来,然后我们再参与拼接,那这样的话就不会有问题了
再次运行打包,看一下打包的结果,那此时我们所看到的结果就是我们所需要的了,当然了,除了module.exports这种方式外,webpack的还允许我们在返回的代码当中直接去使用ESModule的方式去导出.

  1. const marked = require('marked')
  2. module.exports = source => {
  3. const html = marked(source)
  4. console.log(html)
  5. // 两种导出方式:
  6. // return `module.exports=${JSON.stringify(html)}`
  7. return `export default ${JSON.stringify(html)}`
  8. }

第2种方法:
那就是在我们markdown-loader的当中去返回一个html的字符串,然后我们交给下一个loader处理这个htm的字符串
安装一个用于去处理html加载的loader,叫做html-loader

  1. const marked = require('marked')
  2. module.exports = source => {
  3. const html = marked(source)
  4. console.log(html)
  5. return html
  6. }
  1. module: {
  2. rules: [
  3. {
  4. test: /.md$/,
  5. use: ['html-loader', './markdown-loader.js']
  6. }
  7. ]
  8. }

loader,它实际上是一种管道的概念,我们可以将我们此次的这个loader的结果交给下一个loader去处理,然后我们通过多个loader去完成一个功能,如之前所使用的css-loader和style-loader之间的一个配合,包括我们后面还会使用到的,像sass或者less这种loader他们也需要去配合我们刚才所说道的这两种loader,这就是我们loader的工作管道这样一个特性。

二、Webpack插件机制

插件机制的是webpack一个核心特性,目的是为了增强webpack自动化方面的能力。
Loader专注实现资源模块的加载,从而去实现整体项目的打包。
Plugin解决除了资源加载以外的其他的一些自动化工作:

1、clean-webpack-plugin

自动在打包之前去清除dist目录
安装:yarn add clean-webpack-plugin --dev
webpack.config.js

  1. const {CleanWebpackPlugin} = require('clean-webpack-plugin')
  2. plugins: [
  3. new CleanWebpackPlugin()
  4. ]

2、html-webpack-plugin

自动生成使用bundel.js的HTML

  • 安装:yarn add html-webpack-plugin --dev

    1. const HtmlWebpackPlugin = require('html-webpack-plugin')
    2. plugins: [
    3. new CleanWebpackPlugin(),
    4. new HtmlWebpackPlugin()
    5. ]
  • 自动生成HTML文件到dist目录中,根目录下的index.html则不再需要了

  • HTML中自动注入了bundle.js的引用到HTML文件中
  • 增加配置参数生成HTML文件:

    new HtmlWebpackPlugin({
    title: 'Webpack Plugin Sample',
    meta: {
      viewport: 'width=device-width'
    }
    })
    
  • 通过模板文件生成HTML文件, webpack.config.js中指定HtmlWebpackPlugin的template参数

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webpack</title>
    </head>
    <body>
    <div class="container">
      <h1><%= htmlWebpackPlugin.options.title %></h1>
    </div>
    </body>
    </html>
    
    plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Webpack Plugin Sample',
      meta: {
        viewport: 'width=device-width'
      },
      template: './src/index.html'
    })
    ]
    
  • 生成多个HTML页面

    plugins: [
      new CleanWebpackPlugin(),
      // 用于生成index.html 
      new HtmlWebpackPlugin({
        title: 'Webpack Plugin Sample',
        meta: {
          viewport: 'width=device-width'
        },
        template: './src/index.html'
      }),
      // 用于生成about.html 
      new HtmlWebpackPlugin({
        filename: 'about.html'
      })
    ]
    

    3、copy-webpack-plugin

    拷贝那些不需要参与打包的资源文件到输出目录,压缩我们打包结果输出的代码
    安装:yarn add copy-webpack-plugin --dev
    因为这些资源文件一般都比较大,所以插件只会在上线前打包使用,开发过程中经常会打包编译,执行这个插件太消耗资源了 ```javascript const CopyWebpackPlugin = require(‘copy-webpack-plugin’)

new CopyWebpackPlugin( { patterns: [‘public’] } //[‘public’] )


<a name="ZL6wX"></a>
# 三、webpack开发插件
相比于Loader,Plugin拥有更宽的能力范围,Plugin通过钩子机制实现。<br />webpack在工作时有很多环节,便于插件的扩展,几乎在每一个环境都安排了钩子<br />Webpack要求插件必须是一个函数或者是一个包含apply方法的对象。通过在生命周期的钩子中挂载函数实现扩展。
```javascript
class MyPlugin {
  apply (compiler) {
    console.log('MyPlugin 启动')
    compiler.hooks.emit.tap('MyPlugin', compilation => {//emit钩子,在输出文件之前
      // compilation 可以理解为此次打包的上下文
      for (const name in compilation.assets) {
        // console.log(name) // 文件名
       // console.log(compilation.assets[name].source())
        if(name.endsWith('.js')) {
          const contents = compilation.assets[name].source()
          const withoutComments = contents.replace(/\/\*\*+\//g, '')
          compilation.assets[name] = {
            source: () => withoutComments,
            size: () => withoutComments.length
          }
        }
      }
    })
  }
}
plugins: [
  new MyPlugin()
]

四、webpack dev server

自动进行编译:执行yarn webpack —watch会监视文件的变化自动进行打包
自动刷新浏览器:webpack-dev-server,安装:yarn add webpack-dev-server —dev

"scripts":{
  "dev":"webpack server"
}
-----------
"dev": "webpack server --open --host 127.0.0.1 --port 9999"
//--host 配置ip --port配置端口

Dev Server默认只会serve打包输出文件,只要是webpack打包输出的文件都会被访问到,其他静态资源也需要被server

devServer: {
  contentBase: './public',//contentBase额外为开发服务器指定查找资源目录
   open:true,
},
target: 'web' //5以上版本要指定是在web环境下,不然不会自动刷新

五、proxy代理

webpack-dev-server支持配置代理

devServer: {
  contentBase: './public',
    proxy: {
      '/api': {// 以/api开头的地址都会被代理到接口当中
        // http://localhost:8080/api/users -> https://api.github.com/api/users
        target: 'https://api.github.com',
          // http://localhost:8080/api/users -> https://api.github.com/users
          pathRewrite: {
            '^/api': ''
          },
            // 不能使用localhost:8080作为请求GitHub的主机名
            changeOrigin: true, // 以实际代理的主机名去请求
      }
      }
    }