专栏首页WebJ2EE【前端】:模块化 - 打包技术

【前端】:模块化 - 打包技术

1. 核心知识回顾
  1.1. globalThis
  1.2. ES 模块化语法回顾
    1.2.1. 导入
    1.2.2. 导出
    1.2.3. 动态绑定
  1.3. UMD
  1.4. 为什么 ES 模块比 CommonJS 更好?
  1.5. 什么是 "tree-shaking"?
  1.6. mjs 是什么?
  1.7. unpkg 是什么?
  1.8. pkg.module 是什么?
  1.9. commonjs2 是什么?
2. 构建一个库
  2.1. 构建需求?
  2.2. 用 webpack 构建
  2.3. 用 Rollup.js 构建?
  2.4. 用 father-build 构建?

1. 核心知识回顾

1.1. globalThis

  • 在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过 window、self 或者 frames 取到全局对象,但是在 Web Workers 中,只有 self 可以。在 Node.js 中,它们都无法获取,必须使用 global
  • 在松散模式下,可以在函数中返回 this 来获取全局对象,但是在严格模式和模块环境下,this 会返回 undefined。You can also use Function('return this')(), but environments that disable eval(), like CSP in browsers, prevent use of Function in this way.
  • globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis,不必担心它的运行环境。为便于记忆,你只需要记住,全局作用域中的 this 就是 globalThis。

1.2. ES 模块化语法回顾

1.2.1. 导入

  • 命名导入
// Import a specific item from a source module, 
// with its original name.
import { something } from './module.js';

// Import a specific item from a source module, 
// with a custom name assigned upon import.
import { something as somethingElse } from './module.js';
  • 命名空间导入
// Import everything from the source module as an object 
// which exposes all the source module's named exports as properties 
// and methods.
import * as module from './module.js'
  • 默认导入
// Import the default export of the source module.
import something from './module.js';
  • 空导入
// Load the module code, but don't make any new objects available.
// This is useful for polyfills, or when the primary purpose of the imported code 
// is to muck about with prototypes.
import './module.js';
  • 动态导入
// This is useful for code-splitting applications and using modules on-the-fly.
import('./modules.js').then(({ default: DefaultExport, NamedExport })=> {
  // do something with modules.
})

1.2.2. 导出

  • 命名导出
// Export a value that has been previously declared:
const something = true;
export { something };

// Rename on export:
export { something as somethingElse };

// this works with `var`, `let`, `const`, `class`, and `function`
export const something = true;
  • 默认导出
// Export a single value as the source module's default export:
//   1. This practice is only recommended if your source module only has one export.
//   2. It is bad practice to mix default and named exports in the same module, 
//      though it is allowed by the specification.
export default something;

1.2.3. 动态绑定

ES modules export live bindings, not values, so values can be changed after they are initially imported as per.

incrementer.js:

// incrementer.js
export let count = 0;

export function increment() {
  count += 1;
}

main.js:

// main.js
import { count, increment } from './incrementer.js';

console.log(count); // 0

increment();
console.log(count); // 1

increment();
console.log(count); // 2

index.html:

<!DOCTYPE html>
<html>
    <body>
        <script type="module" src="./incrementer.js"></script>
        <script type="module" src="./main.js"></script>
    </body>
</html>

运行结果:

1.3. UMD

UMD:Universal Module Definition(通用模块规范)是由社区想出来的一种整合了CommonJS 和 AMD 两个模块定义规范的方法。

main.js:

export default "Hello World!";

bundle.js:

  • 如果在 cjs 环境下?
  • 如果在 amd 环境下?
  • 导出到全局;(注意 globalThis 的应用)
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
      (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.hello = factory());
}(this, (function () {
  'use strict';
  var main = "Hello World!";
  return main;
})));

1.4. 为什么 ES 模块比 CommonJS 更好?

ES modules are an official standard and the clear path forward for JavaScript code structure, whereas CommonJS modules are an idiosyncratic legacy format that served as a stopgap solution before ES modules had been proposed. ES modules allow static analysis that helps with optimizations like tree-shaking and scope-hoisting, and provide advanced features like circular references and live bindings.

1.5. 什么是 "tree-shaking"?

Tree-shaking, also known as "live code inclusion", is a process of eliminating code that is not actually used in a given project. It is a form of dead code elimination but can be much more efficient than other approaches with regard to output size. The name is derived from the abstract syntax tree of the modules (not the module graph). The algorithm first marks all relevant statements and then "shakes the syntax tree" to remove all dead code. It is similar in idea to the mark-and-sweep garbage collection algorithm. Even though this algorithm is not restricted to ES modules, they make it much more efficient as they allow us to treat all modules together as a big abstract syntax tree with shared bindings.

1.6. mjs 是什么?

On the Web, the file extension doesn’t really matter, as long as the file is served with the JavaScript MIME type text/javascript. The browser knows it’s a module because of the type attribute on the script element.

Still, we recommend using the .mjs extension for modules, for two reasons:

  1. During development, the .mjs extension makes it crystal clear to you and anyone else looking at your project that the file is a module as opposed to a classic script. (It’s not always possible to tell just by looking at the code.) As mentioned before, modules are treated differently than classic scripts, so the difference is hugely important!
  2. It ensures that your file is parsed as a module by runtimes such as Node.js and d8, and build tools such as Babel. While these environments and tools each have proprietary ways via configuration to interpret files with other extensions as modules, the .mjs extension is the cross-compatible way to ensure that files are treated as modules.

Note: To deploy .mjs on the web, your web server needs to be configured to serve files with this extension using the appropriate Content-Type: text/javascript header, as mentioned above. Additionally, you may want to configure your editor to treat .mjs files as .js files to get syntax highlighting. Most modern editors already do this by default.

1.7. unpkg 是什么?

  • unpkg is a fast, global content delivery network for everything on npm.
  • Use it to quickly and easily load any file from any package using a URL like:
    • unpkg.com/:package@:version/:file
      • 例:https://unpkg.com/jquery@3.5.1/dist/jquery.js

1.8. pkg.module 是什么?

pkg.module will point to a module that has ES2015 module syntax but otherwise only syntax features that the target environments support.

Typically, a library will be accompanied with a package.json file (this is mandatory if you're publishing on npm, for example). That file will often specify a pkg.main property - something like this:

{
  "name": "my-package",
  "version": "0.1.0",
  "main": "dist/my-package.js"
}

That instructs Browserify or Webpack or [insert module bundler here] to include the contents of dist/my-package.js – plus any dependencies it has – in your bundle when you call require('my-package') in your app or library.

But for ES2015-aware tools like Rollup, using the CommonJS (or Universal Module Definition) build isn't ideal, because we can't then take advantage of ES2015 module features. So assuming that you've written your package as ES2015 modules, you can generate an ES2015 module build alongside your CommonJS/UMD build:

{
  "name": "my-package",
  "version": "0.1.0",
  "main": "dist/my-package.umd.js",
  "module": "dist/my-package.esm.js"
}

Now we're cooking with gas - my-package continues to work for everyone using legacy module formats, but people using ES2015 module bundlers such as Rollup don't have to compromise. Everyone wins!

1.9. commonjs2 是什么?

CommonJs spec defines only exports. But module.exports is used by node.js and many other CommonJs implementations.

  • commonjs mean pure CommonJs
  • commonjs2 also includes the module.exports stuff.

2. 构建一个库

下面我们选几个主流打包工具

分别构建同一个库

看看它们各自有啥特点

2.1. 构建需求?

  • 库名:webj2ee-numbers
  • 非模块化环境下的访问名:webj2eeNumbers
  • 导入方式:
    • ES2015:
      • import * as webj2eeNumbers from 'webj2ee-numbers'; // ... webj2eeNumbers.wordToNum('Two');
    • CommonJS:
      • const webj2eeNumbers = require('webj2ee-numbers'); // ... webj2eeNumbers.wordToNum('Two');
    • AMD module require:
      • // 关联 lodash cdn requirejs.config({ "paths": { "lodash": "//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min" } }); require(['webj2ee-numbers'], function (webj2eeNumbers) { // ... const x= webj2eeNumbers.wordToNum('Two'); alert(x); });
    • script tag
      • <!doctype html> <html> <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script> <script src="https://unpkg.com/webj2ee-numbers"></script> <script> // ... // Global variable const x= webj2eeNumbers.wordToNum('Five') console.log(x); </script> </html>
  • 外部依赖:
    • lodash 是外部依赖,不应打包到 webj2ee-numbers 中。
  • 源码结构:

2.2. 用 webpack 构建

webpack.config.js:

const path = require('path');
module.exports = {
    mode: 'development',
    entry: './src/index.js',
    devtool: "none",
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'webj2ee-numbers.js',
        library: 'webj2eeNumbers',
        libraryTarget: 'umd',
    },
    externals: {
        lodash: {
            commonjs: 'lodash',
            commonjs2: 'lodash',
            amd: 'lodash',
            root: '_',
        },
    },
};

dist/webj2ee-numbers.js:

2.3. 用 Rollup.js 构建?

rollup.config.js:

import json from 'rollup-plugin-json';

export default {
    input: 'src/index.js',
    output: {
      file: 'dist/webj2ee-numbers.js',
      format: 'umd',
      name: "webj2eeNumbers",
      globals: {
        lodash: '_'
      }
    },
    external: ['lodash'], // 将 lodash 视为外部模块
    plugins: [ json() ]
  };
  

dist/webj2ee-numbers.js:

2.4. 用 father-build 构建?

  • 基于 docz 的文档功能
  • 基于 rollup 和 babel 的组件打包功能
  • 支持 TypeScript
  • 支持 cjs、esm 和 umd 三种格式的打包
  • esm 支持生成 mjs,直接为浏览器使用
  • 支持用 babel 或 rollup 打包 cjs 和 esm
  • 支持多 entry
  • 支持 lerna
  • 支持 css 和 less,支持开启 css modules
  • 支持 test
  • 支持用 prettier 和 eslint 做 pre-commit 检查

.fatherrc.js:

  • external 可通过 dependencies 和 peerDependencies 的约定实现。
export default {
    entry: 'src/index.js',
    esm: "rollup",
    cjs: "rollup",
    umd: {
        name: "webj2eeNumbers",
        globals:{
            lodash: '_'
          }
    }
}

打包结果:

参考:

globalThis: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/globalThis UMD: https://github.com/umdjs/umd mjs: https://v8.dev/features/modules#mjs unpkg: https://unpkg.com/ pkg.module: https://github.com/rollup/rollup/wiki/pkg.module commonjs2: https://github.com/webpack/webpack/issues/1114 es模块化语法回顾: https://www.rollupjs.org/guide/en/#es-module-syntax webpack - Libraries https://webpack.js.org/guides/author-libraries/ https://webpack.js.org/configuration/externals/ Rollup.js: https://www.rollupjs.org/guide/en/#faqs https://www.rollupjs.com/ father-build: https://www.npmjs.com/package/father-build https://github.com/umijs/father 利用 umi-library(father) 做组件打包: https://www.bilibili.com/video/av47853431


本文分享自微信公众号 - WebJ2EE(WebJ2EE),作者:WEBJ2EE

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • React:Redux源码分析

    Redux 是JavaScript 应用的状态管理容器,提供集中式、可预测的状态管理。

    WEBJ2EE
  • 【自动化测试】【Jest-Selenium】(01)—— Jest 入门

    按照软件工程自底而上的概念,前端测试一般分为单元测试(Unit Testing )、集成测试(Integration Testing)和端到端测试(E2E Te...

    WEBJ2EE
  • CSS:10分钟搞定Flex布局

    Flexbox Layout(弹性盒子布局),是CSS3的一种新型布局模式,给很多CSS老大难问题提供了优雅的解决方案(例如:垂直居中

    WEBJ2EE
  • 后Selenium时代--Cypress 小试牛刀

    Cypress 支持 Windows64、Windows32、Linux64、MacOS64 版本

    FunTester
  • ethers.js教程

    如果你已经在以太坊上开发过DApp,那你在前端JavaScript中可能用过web3.js。Ethers.js则是一个轻量级的web3.js替代品,在本文中,我...

    用户1408045
  • Haproxy简介、安装、配置、算法和监控平台

    版权声明:欢迎交流,菲宇运维! https://blog.csdn.net/bbwangj/art...

    菲宇
  • 【差别】3-6K和10-15K的前端要求有啥区别?怎么达到?

    先上图片, (1)首先是3-6K要求的, ? (2)然后是10-15K要求的, ? 差别: (1)、3-6K要求三年要求,10-15K的要求五年要求; 这个并...

    web前端教室
  • 学习前端开发的几个假象

    1,你并不喜欢前端开发,也不喜欢js,你只是听说前端工资高。 许多人学不进去或是学的不太顺利,根本原因在于即不了解前端开发,更谈不上喜欢前端开发。很早以前,有一...

    web前端教室
  • 为什么说机器学习不是人工智能?

    我们知道的远比我们说出来的要多得多,我们不知道的远比我们知道的要多得多,我们不知道我们不知道的远比我们不知道的要多得多……

    马上科普尚尚
  • 深入剖析 WebKit

    1990年 Berners-Lee 发明了 WorldWideWeb 浏览器,后改名 Nexus,在1991年公布了源码。

    用户7451029

扫码关注云+社区

领取腾讯云代金券