webpack学习笔记|8月更文挑战

931 阅读3分钟

screenshot-20210731-172113.png

webpack简介

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle

webpack官网

webpack中文文档

写在前面

一开始主要是参考官方文档来学习,后来发现阮一峰老师在github上有webpack的demo可以结合学习,这篇文章主要是以这个demo为主线来记录。

先贴一下项目的地址 webpack-demos

有几个要注意的地方,大佬的demo是几年前写的了,语法和配置上可能会和新版本有冲突

  1. output 默认会新建一个 dist 文件夹把打包好的文件放在里面

  2. package.json 里面的build指令 把 webpack -p 改成 webpack

  3. output.filename,注意要写相对路径,比如 ./dist/bundle.js 。如果这样写是错误的,dist/bundle.js,会被解析为绝对路径然后报错。

  4. 在引入插件的时候有可能会报错

    TypeError: compiler.plugin is not a function at CommonsChunkPlugin.apply

    遇到类似于这样的报错,可能是webpack版本对应的插件版本的问题,需要根据实际情况调整。

webpack-demos

1. demo01 入口文件

// webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};

一个简单的案例,定义入口和输出

2. demo02 多入口文件

// webpack.config.js

module.exports = {
  entry: {
    bundle1: './main1.js',
    bundle2: './main2.js'
  },
  output: {
    filename: '[name].js'
  }
};

允许多个入口文件,这里的entry变成了一个对象。

output 中 filename 的值为 [name].js,这里的 name 是一个变量,对应着 entry 中的key

3. demo03 Babel-loader

loader 是一个文件加载器

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

这个案例中需要 Babel-loader 来转换JSX文件。

最外层是 module 对象,在 module.rules 定义转换规则,

规则对象的属性包括

// webpack.config.js

module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,                      // 检测匹配什么类型的文件
        exclude: /node_modules/,              // include和exclude来包含或者排除范围
        use: {                                // 选用什么loader
          loader: 'babel-loader',             // loader的名称
          options: {                          // loader 的 配置项
            presets: ['es2015', 'react']      // babel 的Babel的预设插件Babel -preset-es2015和Babel -preset-react
          }
        }
      }
    ]
  }
};

4. demo04 CSS-loader

// webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules:[
      {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader' ]
      },
    ]
  }
};

Webpack 允许您在 JS 文件中包含 CSS,然后使用CSS-loader预处理 CSS 文件。

注意这里引入了两个CSS-loader

css-loader 用来读取CSS文件

style-loader<style>标签插入HTML中。

5. demo05 图像加载器

// webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules:[
      {
        test: /\.(png|jpg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      }
    ]
  }
};

打包后发现打包的文件夹里只有一张图片,???打包前不是有两张图片吗

原来是小图片进行了转换

url-loader会将图片进行转换。如果图片小于一定的大小,就会转换为Data URL;否则,转换为普通 URL。

这个例子中的小图片被转换成了Data URL,所以没有被打包成图片。

这个一定大小可以在 option.limit 中进行设置。

下面是官网的原文

After launching the server, small.png and big.png have the following URLs.

<img src="data:image/png;base64,iVBOR...uQmCC">
<img src="4853ca667a2b8b8844eb2693ac1b2578.png">

6. demo06 CSS 模块

// webpack.config.js

module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015', 'react']
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: 'style-loader'
          },
          {
             loader: 'css-loader',
             options: {
               modules: true
             }
          }
        ]
      }
    ]
  }
};

<!-- index.html -->

<html>
<body>
  <h1 class="h1">Hello World</h1>
  <h2 class="h2">Hello Webpack</h2>
  <div id="example"></div>
  <script src="./bundle.js"></script>
</body>
</html>
// main.jsx

var React = require('react');
var ReactDOM = require('react-dom');
var style = require('./app.css');

ReactDOM.render(
  <div>
    <h1 className={style.h1}>Hello World</h1>
    <h2 className="h2">Hello Webpack</h2>
  </div>,
  document.getElementById('example')
);

/* app.css */

.h1 {
  color:red;
}

:global(.h2) {
  color: blue;
}

通过css-loader?modules(查询参数模块)启用CSS 模块

通过:global(selector)将其关闭,也就是恢复为全局样式。

上面的 .h1 将作为局部样式, .h2 将作为全局样式。

这里的局部样式是指打包的模块内部。

demo的效果如图片

image.png

从demo的效果可以看到,

第一个h1为黑色,第二个h1变成了红色。

这是因为 .h1 是局部样式,只有第二个h1可以应用(处于局部样式的作用域内,即打包的模块中)。

第一个和第二个h2都是蓝色。

这是因为 .h2 是全局样式,全局作用域内的元素都可以应用上。


这个是怎么做到的呢?其实在浏览器里检查一下元素就知道了。

image.png

可以发现,打包后实际点的类名变了。

在打包的过程中,webpack自动将 .h1 的类名转换了,用以保证其只在特定作用域中生效。

而 .h2 是全局样式,没有进行转换。

但是这里要注意渲染部分的写法,

需要传入变量来定义类名,className={style.h1}

如果只是写 “h1” 是无法生效的,会渲染为普通的字符串。

// main.jsx

var React = require('react');
var ReactDOM = require('react-dom');
var style = require('./app.css');

ReactDOM.render(
  <div>
    <h1 className={style.h1}>Hello World</h1>
    <h2 className="h2">Hello Webpack</h2>
  </div>,
  document.getElementById('example')
);

7. demo07 UglifyJs 插件

// webpack.config.js

var webpack = require('webpack');
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new UglifyJsPlugin()
  ]
};

主要是具体插件的使用。

这个插件的功能是简化js代码。

在plugins属性中配置要使用的插件,记得要new

8. demo08 入口文件

// webpack.config.js

var HtmlwebpackPlugin = require('html-webpack-plugin');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new HtmlwebpackPlugin({
      title: 'Webpack-demos',
      filename: 'index.html'
    }),
    new OpenBrowserPlugin({
      url: 'http://localhost:8080'
    })
  ]
};

主要是讲解如何引入第三方插件。

这个案例中的

html-webpack-plugin 可以帮助创建 index.html

open-browser-webpack-plugin可以在 Webpack 加载时打开一个新的浏览器选项卡。

在新建插件实例的时候可能需要传递一些参数,具体可以参考插件文档。

这里运行时报错了,好像是要安装和webpack版本对应的插件版本。

9. demo09 环境标志

// webpack.config.js

var webpack = require('webpack');

var devFlagPlugin = new webpack.DefinePlugin({
  __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
});

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [devFlagPlugin]
};

// package.json

var webpack = require('webpack');

var devFlagPlugin = new webpack.DefinePlugin({
  __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
});

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [devFlagPlugin]
};

可以在带有环境标志的开发环境中启用某些代码。

在运行的时候将环境变量传递给webpack,就可以在不同的环境中做对应的操作

10. demo10 代码拆分

// webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};

// main.js

require.ensure(['./a'], function(require) {
  var content = require('./a');
  document.open();
  document.write('<h1>' + content + '</h1>');
  document.close();
});

代码拆分的结构如图

image.png


Webpackrequire.ensure用来定义一个分割点

require.ensure告诉 Webpack./a.js应该bundle.js与单个块文件分离并构建到单个块文件中。

require.ensure是webpack所独有的,可以被es6的import取代:

感觉理解得不是特别好,建议参考这篇文章 zhuanlan.zhihu.com/p/82918552

11. demo11 使用 bundle-loader 进行代码拆分

// webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};

// main.js

var load = require('bundle-loader!./a.js');

load(function(file) {
  document.open();
  document.write('<h1>' + file + '</h1>');
  document.close();
});

代码拆分的结构如图

image.png


代码拆分的另一种方法。注意loader的串联。

require('bundle-loader!./a.js')告诉 Webpacka.js从另一个块加载。

现在的WebPack将建设main.jsbundle.jsa.js0.bundle.js

12. demo12 通用模块

// webpack.config.js

var webpack = require('webpack');

module.exports = {
  entry: {
    bundle1: './main1.jsx',
    bundle2: './main2.jsx'
  },
  output: {
    filename: '[name].js'
  },
  module: {
    rules:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015', 'react']
          }
        }
      },
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "commons",
      // (the commons chunk name)

      filename: "commons.js",
      // (the filename of the commons chunk)
    })
  ]
}

// main1.jsx

var React = require('react');
var ReactDOM = require('react-dom');
ReactDOM.render(
  <h1>Hello World</h1>,
  document.getElementById('a')
);


// main2.jsx

var React = require('react');
var ReactDOM = require('react-dom');
ReactDOM.render(
  <h2>Hello Webpack</h2>,
  document.getElementById('b')
);


<!-- index.html -->

<html>
  <body>
    <div id="a"></div>
    <div id="b"></div>
    <script src="commons.js"></script>
    <script src="bundle1.js"></script>
    <script src="bundle2.js"></script>
  </body>
</html>

当多个脚本有共同的块时,可以使用CommonsChunkPlugin将共同的部分提取到一个单独的文件中,这对于浏览器缓存和节省带宽很有用。

和上面的拆分相反,这里讲的是合并,把通用的逻辑从多个文件中抽离出来。

在这个例子中,main1.jsx和main2.jsx的公共逻辑是reactreact-dom,被抽离出来保存在另外的文件中。

13. demo13 供应商区块

// webpack.config.js

var webpack = require('webpack');

module.exports = {
  entry: {
    app: './main.js',
    vendor: ['jquery'],
  },
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'vendor.js'
    })
  ]
};

// main.js

var $ = require('jquery');
$('h1').text('Hello World');

);


<!-- index.html -->

<html>
  <body>
    <h1></h1>
    <script src="vendor.js"></script>
    <script src="bundle.js"></script>
  </body>
</html>


可以使用 CommonsChunkPlugin 从脚本中提取供应商库到一个单独的文件中。

这里用的是jQuery。 entry.vendor: ['jquery']告诉 Webpackjquery应该包含在公共块中vendor.js


这样的话在引用之前需要require,如果需要某个模块在全局范围的每一个模块中可用,使用ProvidePlugin( Official doc ) 自动加载模块,而不必在任何地方 import or require 它们。

// main.js

$('h1').text('Hello World');


// webpack.config.js
var webpack = require('webpack');

module.exports = {
  entry: {
    app: './main.js'
  },
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    })
  ]
};

在这种情况下,jquery.js需要全局加载。

14. demo14 入口文件

// webpack.config.js

module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015', 'react']
          }
        }
      },
    ]
  },
  externals: {
    // require('data') is external and available
    //  on the global var data
    'data': 'data'
  }
};

// main.jsx

var data = require('data');
var React = require('react');
var ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>{data}</h1>,
  document.body
);

下面是官网的原文

Attention, Webpack will only build bundle.js, but not data.js.

you require data as a module variable in your script. but it actually is a global variable.


如果你想使用一些全局变量,又不想将它们包含在Webpack bundle中,可以externalswebpack.config.js官方文档)中启用字段。

externals中可以配置某些模块,webpack编译打包时不处理它,却可以引用到它。

可以将reactreact-dom放入externals,这将大大减少bundle.js构建时间和构建大小。

15. demo15 React router

+---------------------------------------------------------+
| +---------+ +-------+ +--------+                        |
| |Dashboard| | Inbox | |Calendar|      Logged in as Jane |
| +---------+ +-------+ +--------+                        |
+---------------------------------------------------------+
|                                                         |
|                        Dashboard                        |
|                                                         |
|                                                         |
|   +---------------------+    +----------------------+   |
|   |                     |    |                      |   |
|   | +              +    |    +--------->            |   |
|   | |              |    |    |                      |   |
|   | |   +          |    |    +------------->        |   |
|   | |   |    +     |    |    |                      |   |
|   | |   |    |     |    |    |                      |   |
|   +-+---+----+-----+----+    +----------------------+   |
|                                                         |
+---------------------------------------------------------+

对React router不是特别熟悉,先记录


总结

1. 理解一些核心概念

2. webpack简单配置

记得先安装webpackwebpack-cli

 const path = require('path');

 module.exports = {
   entry: './src/index.js',                         //打包的入口文件
   mode: 'development',                             //打包的环境
   output: {                                        //打包的输出文件
     filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
   module: {                                        //加载器,用来加载各种类型的文件
     rules: [
       {
         test: /.css$/i,
         use: ['style-loader', 'css-loader'],
       },
     ],
   },
   plugins: [                                       //插件,用来提供各种功能
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
 };

3. 参考资料

深入浅出 Webpack

segmentfault.com/a/119000000…

zhuanlan.zhihu.com/p/65574428