让前端飞Web前端之路

Webpack原理-从前端模块化开始

2019-07-28  本文已影响5人  那少妇

当前主流 JS 模块化方案

注意:cjs、amd、cmd、 ES Modules 都是只规范,所以可能对应有多种实现

下面就对各个模块化方案做简单说明

无模块化时代

一把梭
<script src="jquery.js"></script>
<script src="jquery_scroller.js"></script>
<script src="main.js"></script>
<script src="other1.js"></script>
<script src="other2.js"></script>
<script src="other3.js"></script>

无模块化时代的问题

CommonJS 规范

function compiledWrapper(exports, require, module, __filename, __dirname) {
  // 插入文件中的代码
  // 返回导出对象
  return module.exports
}
compiledWrapper.call(exports, exports, require, module, filename, dirname)

lib.js

// lib.js
let counter = 3
function incCounter() {
  counter++
}
module.exports = {
  counter,
  incCounter
}

index.js

// index.js
const mod = require('./lib') // 此处输出值?
console.log(mod.counter)
mod.incCounter() // 此处输出值?
console.log(mod.counter)

a.js

// a.js
console.log('a starting')
exports.done = false
const b = require('./b.js')
console.log('in a, b.done = %j', b.done)
exports.done = true
console.log('a done')

b.js

// b.js
console.log('b starting')
exports.done = false
const a = require('./a.js')
console.log('in b, a.done = %j', a.done)
exports.done = true
console.log('b done')

main.js

// main.js
console.log('main starting')
const a = require('./a.js')
const b = require('./b.js')
console.log('in main, a.done = %j, b.done = %j', a.done, b.done)

AMD 规范

require.config({
  paths: {
    // 如果第一个加载失败就会加载第二个
    jquery: ['lib/jquery.min', 'lib/jquery'],
    lodash: 'lib/lodash.min',
    main: './mian' // 入口文件
  }
})

定义模块

/**
* 定义模块,当依赖加载完成后执行回调
* 回调可返回值,返回值会被导出到外部使用
* @param {String} id 模块名称,可省略
* @param {Array} dependencies 依赖的模块
* @param {Function} factory 回调函数
*/
define(id?, dependencies?, factory);

define(['jquery'], function($) {
  $('body').css({ background: 'red' })
  // 导出log函数
  return (...args) => console.log('自定义log', ...args)
})

加载模块

/**
 * 加载模块
 * @param {Array} deps 要加载的模块
 * @param {Function} callback 加载成功回调,回调参数为加载模块导出对象
 * @param {Function} errback 加载失败回调
 */
requirejs(deps, callback, errback)
require(['main'], log => {
  log('我成功加载了‘)
  // do something...,也可以在这里继续require其他js文件
})

requirejs 使用示例

.
├── index.html
└── js
    ├── lib
    │   ├── jquery.js
    │   ├── lodash.js
    │   └── require.js
    ├── main.js
    └── time.js
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>requirejs-demo</title>
  </head>
  <body>
    <h1 id="time"></h1>
    <script src="./js/lib/require.js" data-main="./js/main.js"></script>
  </body>
</html>
requirejs.config({
  baseUrl: '/js/‘,
  paths: {
    jquery: './lib/jquery‘,
    lodash: './lib/lodash‘
  }
})�require(['jquery', './js/time.js'], ($, time) => {
  $('#time').text('TIME: ' + time.getTime())
  setInterval(() => {
   $('#time').text('TIME: ' + time.getTime())
  }, 1000)
})
define(['jquery', 'lodash'], ($, _) => ({
  getTime() {
    const time = new Date()
    const year = time.getFullYear()
    const month = _.padStart(time.getMonth() + 1, 2, '0‘)
    const date = _.padStart(time.getDate(), 2, '0‘)
    const hour = _.padStart(time.getHours(), 2, '0‘)
    const minute = _.padStart(time.getMinutes(), 2, '0‘)
    const second = _.padStart(time.getSeconds(), 2, '0‘)
    return `${year}/${month}/${date} ${hour}:${minute}:${second}`
  }
}))

CMD 规范

// CMD
// 代码写起来有同步require的感觉
define((require, exports, module) => {
  const $ = require('jquery‘)
  $('title').text('hello')
})
// AMD
// 明显的异步风格
define(['jquery'], $ => {
  $('title').text('hello')
})

seajs 中 require 书写约定

  1. 正确拼写 require
// 错误!
define(function(req) {
  // ...
}) // 正确!
define(function(require) {
  // ...
})
  1. 使用直接量
// 错误!
require(myModule) // 错误!
require('my-' + 'module') // 错误!
require('MY-MODULE'.toLowerCase()) // 正确!
require('my-module')
  1. 不要修改 require
// 错误 - 重命名 "require"!
var req = require,
  mod = req('./mod') // 错误 - 重定义 "require"!
require = function() {} // 错误 - 重定义 "require" 为函数参数!
function F(require) {} // 错误 - 在内嵌作用域内重定义了 "require"!
function F() {
  var require = function() {}
}

seajs 隐藏坑

function func(require, exports, module) {
  const $ = require('jquery‘)
  console.log($)
}
func.toString = () => '() => {}'
define(func)

seajs 对于 require 和 define 函数的特殊要求是由于,seajs 原理导致的,seajs 的执行流程大致如下


seajs执行流程

seajs 使用示例

.
├── index.html
└── js
    ├── lib
    │   ├── jquery.js
    │   ├── lodash.js
    │   └── sea.js
    ├── main.js
    └── time.js
<!DOCTYPE html>
<html>
  <head>
    <meta charset="“UTF-8”" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>seajs-demo</title>
  </head>
  <body>
    <h1 id="time"></h1>
    <script src="./js/lib/sea.js" data-main="./js/main.js"></script>
    <script>
       seajs.config({
         base: '/js/‘,
         alias: {
           jquery: './lib/jquery‘,
           lodash: './lib/lodash‘
         }
      })
      // 加载入口模块
      seajs.use('./js/main.js')
    </script>
  </body>
</html>
define((require, exports, module) => {
  const $ = require('jquery‘)
  const time = require('./time.js‘)
  $('#time').text('TIME: ' + time.getTime())
  setInterval(() => {
    $('#time').text('TIME: ' + time.getTime())
  }, 1000)
})
define((require, exports, module) => {
  module.exports = {
    getTime() {
      const $ = require('jquery‘)
      const _ = require('lodash‘)
      const time = new Date()
      const year = time.getFullYear()
      const month = _.padStart(time.getMonth() + 1, 2, '0‘)
      const date = _.padStart(time.getDate(), 2, '0‘)
      const hour = _.padStart(time.getHours(), 2, '0‘)
      const minute = _.padStart(time.getMinutes(), 2, '0‘)
      const second = _.padStart(time.getSeconds(), 2, '0‘)
      return `${year}/${month}/${date} ${hour}:${minute}:${second}`
    }
  }
})

ES Modules

lib.mjs

// lib.mjs
export let counter = 3

export function incCounter() {
  counter++
}

index.mjs

// index.mjs
import * as mod from './lib’

// 此处输出值?�console.log(mod.counter)
mod.incCounter()

// 此处输出值?
console.log(mod.counter)

循环引用

请问执行node --experimental-modules main.mjs后会输出什么内容
a.mjs

// a.mjs
import { bar } from './b.mjs'
console.log('a.mjs')
console.log(bar)
export let foo = 'foo'

b.mjs

// b.mjs
import { foo } from './a.mjs'
console.log('b.mjs')
console.log(foo)
export let bar = 'bar'

main.mjs

// main.mjs
import './a.mjs'

循环依赖问题

循环依赖

相关链接

关于作者

欢迎关注 nashaofu

上一篇 下一篇

猜你喜欢

热点阅读