Today we are going to introduce front-end modular development.

Modularity is one of the most important front-end development paradigms today, and mastering modularity is always a requirement in a hiring JD. Therefore, according to the following questions from the purpose of modularization, evolution process began to comb, in order to quickly understand the present/future necessary to master the [front-end modularization development], this is the beginning of the content.

  1. Purpose, concept, and solution of modularity
  2. The evolution of modularity
  3. Modular standards and specifications
  4. Modular ES Modules Basic features
  5. ES Module import and export (most core and common functions)
  6. ES Modules Import usage
  7. ES Modules Directly export imported members (secondary export)
  8. ES Modules Polyfill compatible solution
  9. ES Modules in Node.js

1. Purpose and concept of modularization

With the increasingly complex of the front-end application, our program code has been expanded to have to spend a lot of time to management level, and the modular is the mainstream form of organization code, it is through our complex code according to different divided into different modules of function separate maintenance way to improve the efficiency of our development, reduce maintenance costs.

2. The evolution of modularity

Because front-end technology standards did not anticipate the scale of the front-end industry today, we had a lot of problems implementing front-end modularity (because there were no standards). Now these problems have been solved by some standards or tools, so we will first understand the front-end industry in the process of implementing modular standards encountered problems and solve the problem of thought design, in order to gradually understand the evolution of today’s modular process. Let’s start with one of the earliest forms of modularization: file partitioning.

  • File division mode

In this way, each function and its dependent state, data, etc. are stored in separate files, and each file is treated as a module by convention, as shown in the following figure:

The way to use a module is in a fileDirect reference via the script tagAs shown in figure:Obviously, variables and functions defined in each file will be globally scoped in this way. The dangers are obvious: file namespaces can easily conflict, cause global scope contamination, and module internal objects can be accessed/modified arbitrarily from outside, leading to serious coupling and difficult maintenance… There are many other hazards, but we will briefly state them as follows:

  • Name conflict problem
  • Contamination global scope
  • Coupling and security issues
  • Unable to manage module dependencies

That’ll make it easier to remember. So the early documentation approach was really all about convention, and when the project reached a certain size, it didn’t work.

  • Namespace mode

In order to solve the problem of naming conflicts, a namespace approach has been evolved from the file partition approach to achieve modularity. In this approach, each file only exposes one global object, and all members of the file only need to be mounted to this object, as shown in the following figure:

In this way, each module formed by a file has its own namespace.

However, this approach still does not solve the problem of module internal members being accessed/modified arbitrarily from outside: modules still have no private space of their own, and dependencies between modules are not resolved.

  • IIFE (Immediate-Invoked Function Expression) the way in which the Function is Invoked Immediately

The IIFE approach declares an anonymous function inside a file (module) and executes it immediately. The purpose of this is obvious: all members of the file will be placed inside the immediately executed function, thus forming a separate scope for the file. In this case, the external can’t access the file (module) inside? So we need to mount the members that need to be used/accessed externally to the global object mounted on the window exposed by the file (module) by using the namespace mentioned above. The following is an example:

This approach realizes the concept of private members, outside the module is not able to access/modify its internal members that are not exposed through the global object, these private members can only be accessed/modified through the form of closure, that is, exposed module methods, which ensures the security of private members. As shown in the figure:

We can also use the parameters of the self-executing function as the dependency declaration of the file (module), which makes the dependency of the module more obvious.

As above we can see directly that this file (module) depends on JQuery.

The above is the front-end industry in the early without any tools and specifications, the practice of modular landing exploration of several available landing ways. But these ways are still unable to solve other problems well, basic it is agreed by the means to achieve them, and between different developers, even by the same means to achieve modular also will appear different on some details, it all comes down to modular there is still no unified norms and standards.

3. Modular standards and specifications

We need a standard to regulate the modularity implementation, and we also need to solve the problem of module loading in the above approach (direct loading via script tags).

Loading directly through the script tag means that our module loading is not controlled by the code. Once it gets a bit cumbersome to maintain, consider this: The experience of using a module in your code that forgets to use HTML references is pretty bad. Or we remove the use of the module from the code, and then we forget to remove the reference to the module in the HTML, which is even worse. So we need common code to automatically load modules through code.

So what we need is: modular standard + module loader (module loading base library).

  • CommonJS specification

NodeJS is a set of standards that must be followed in NodeJS. The standards are described as follows:

  • A file is a module
  • Each module has a separate scope
  • Exports members through module.exports
  • Load the module via the require function

It is easy to see that these brief standards address the same problems that earlier modular approaches addressed: scope, namespace, partition by function, member export security, and so on

So since CommonJS is a modular standard for Node environment, we will have problems using it in browser environment. The CommonJS convention loads modules synchronously at startup. This convention makes sense in Node environments, but it can be inefficient if you switch to browsers: The CommonJS specification was not chosen in the early days of front-end modularization because pages were bound to be loaded with synchronous mode requests. So which specification should be adopted on the browser side?

  • AMD (Asynchronous Module Definition) specification

Combined with the characteristics of the browser, THE AMD specification came into being.

Require.js is a well-known library that implements the AMD specification and is a powerful module loader in its own right.

The require.js convention requires a method called define to define a module:

Definition of parameters:

  • The first parameter: module name
  • The second parameter: the module’s dependency
  • The third argument: a function whose argument corresponds to the dependent, which is the derived member of the previous dependency. The return () function provides a private space for the module to export required external members.

Require.js also specifies a method called Require to load a module:

Require.js inside Require actually loads modules and executes module code by creating script tags. Most third-party libraries currently support the AMD specification, but AMD still has a few shortcomings:

  • AMD is relatively complex to use and less maintainable
  • Module JS file requests frequently (underlying script)

At the same time ali also produced a sea.js which implements the Common Module Definition CMD specification. The purpose is to make the code look similar to using the CommonJS specification, so as to reduce the learning cost of developers to understand.

  • ES Modules specification (Best practices for modularity)

At present, the front-end modular specification is unified as follows: CommonJS specification is used on Node.js and ES Modules specification is used on browser. ES Modules is a feature proposed by ES2015. At the beginning, many browsers do not support ES Modules, but with time and the popularity of webpack and other tools, ES Modules gradually become the mainstream front-end modular specification of the browser. The current browsers support the following:

4. Basic features of ES Modules

ES Modules is a JavaScript language level modular system specification, since it is language level, then we must first understand its syntax and features.

  • Syntax is the same as using script tags to write JS code, except that type=”module” is added as follows:
<! DOCTYPE html><html lang="en">
  <body>
    <script type="module">
      console.log('this is es module')
    </script>
  </body>
</html>

Copy the code
  • ESM automatically adopts strict mode, ignoring ‘use strict’

That is, even without use strict, its internal JS will be strictly mode executed.

Strictly, this is undefined; In non-strict mode, this is window

<! DOCTYPE html><html lang="en">
  <body>
    <script type="module">
      console.log(this)
    </script>
  </body>
</html>

Copy the code
  • Each ES Module runs in a separate private scope

This means that we can use multiple scripts to split code into different modules, and each module’s scope will be independent of each other.

<! DOCTYPE html><html lang="en">
  <body>
    <script type="module">
      var foo = 100
      console.log(foo)
    </script>
    <script type="module">
      console.log(foo)
    </script>
  </body>
</html>

Copy the code

After running, see the following figure:

    

  • ESM requests external JS modules through CORS

Since it is CORS, it means that the JS referenced by SRC must be homologous, otherwise there will be cross-domain problems.

<! DOCTYPE html><html lang="en">
  <body>
    <script type="module" src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
  </body>
</html>
Copy the code

We can see that the request was aborted by the browser, so it is important to know that if we use ESM to import resources, the service must support CORS, otherwise we will get an error.

  • The ESM script tag delays script execution

Speaking of deferential script execution, we can think of the script tag’s defer attribute, which in ESM’s script tag is equivalent to adding a defer attribute.

By default, our web page executes JS first and then renders, while delayed execution will cause the page to finish rendering before executing the JS code.

六四屠杀

5. ES Module import and export (most core and common functions)

As mentioned above, ESMs have independent scopes, so if you want to use a variable/method declared in a module, you always need to export the variable/method in the module and import it where you want to use it. So obviously, if a module doesn’t export any variables/methods, then you can’t import anything through the import method. Without further ado, let’s go straight to an example:

  • Basic import and export/batch export

index.html

<! DOCTYPE html><html lang="en">
  <body>
    <script type="module" src="./app.js"></script>
  </body>
</html>
Copy the code

app.js

// Import it in batches and use it normally
import { text, testFunc } from './module.js'

console.log(text)

testFunc()
Copy the code

module.js

// The first way to export - export the variable separately
export const text = 'hello, this is es module'

const testText = 'this is also es module' // This variable is not exported and therefore cannot be used externally

const testFunc = () = > {
  console.log(testText)
}
// The second export method - batch export variables/methods
export { testFunc }
Copy the code

(If the operation fails due to CORS problems, browser-sync is required to start the service.)

Browser-sync: NPM install -g browser-sync Use: browser-sync. –files **/*.js files Parameter to listen for file changes

  • Renaming imports and exports

We can rename variables/methods during import or export to prevent naming conflicts between modules

const text = 'hello, this is es module'
// Rename when exporting
export { text as  appText}
Copy the code
// Rename the export
import { appText as t } from './module.js'
console.log(t)
Copy the code
  • Default import and export

Can we use the default keyword to define what a module exports by default, so that we can easily import directly (without curly braces) when importing

const text = 'hello, this is es module'
export default text
Copy the code
import text from './module.js'
console.log(text)
Copy the code
  • ** Points to note about import and export **
    • Export {text}, the curly braces used by export are not a shorthand for object literals, but a fixed syntactic usage
    • Export default {text}, where the curly braces are the literal form of the object
    • Import {appText as t} from ‘./module.js’
    • Export exports a reference to a variable/method, that is, the memory address of the variable/method is exported instead of a copy of the export
    • References exported by the ESM cannot be modified externally and are automatically converted to read-only constants when imported

ES Modules Import usage

  • Do not omit file extensions during import
//.js file extensions cannot be omitted. Omitting them will result in an error
import text from './module.js'
Copy the code
  • Even the index file in the imported directory must be filled in completely. The index file in the imported directory is not exported by default
// Even if the import directory index file, may be filled in completely
import text from './utils/index.js'
Copy the code
  • Dot + slash cannot be omitted
// The beginning of the./ is not omitted, if omitted, ES Modules will think we are loading a third-party library
import text from './module.js'
// Instead of using the relative path of dot + slash, we can also use the absolute path of slash, which will be looked up from the site root by default
import text from '/module.js'
// We can also import modules with full urls
import text from 'http://localhost:3000/test/module.js'
Copy the code
  • Pure introduction and trigger use
// The requirement is that our script does not need to be exported, but loaded directly, so we can load the module like this
import {} form './module.js'
// The shorthand is as follows
import './module.js'
Copy the code
  • The asterisk * is used with all extract and rename
import * as mod form './module.js'
console.log(mod.text)
Copy the code
  • Dynamically introduce modules while the script is running

There will always be a need for module names to be available at script runtime, or to be imported in an if condition where the import keyword is only supported at the top of the world and we can’t use import like this

// You can't use it like this
const modulePath = './module.js'
import { text } from modulePath
// You can't do that either
if(true) {import { text } from './module.js'
}
Copy the code

ESM provides a global function to dynamically import modules as follows:

import('./module.js').then(function (module) {
	console.log(module)})Copy the code

Because module loading is an asynchronous process, this function returns a Promise

  • **** and batch export

Suppose we have the following module, which uses default for the default export and also exports variables/methods in batches

const text = 'hello, this is es module'

const testText = 'this is also es module'

const testFunc = () = > {
  console.log(testText)
}

// Batch export
export { testFunc }
// Export defaults
export default {
	text, testFunc
}
Copy the code

If we want to import both the exported objects by default and the exported objects in batches, how should we import them?

// modduleObj is the default exported object of the module
// The curly braces are still the batch imported variables/methods
import modduleObj, { testFunc } from './module.js'
Copy the code

7. ES Modules Directly export imported members (secondary export)

// The first case
// Use the export keyword to export the file
export { text } from './module.js'
// Of course this export only applies to the following form
// module.js
const text = 'this is es module'
export { text }

// The second case
// Export the default export using the default rename during import
export { default as mod} from './module.js'
// This export is for the default everywhere form
// module.js
const text = 'this is es module'
export default { text }
Copy the code

8. ES Modules Polyfill

Because ES Modules is a modular standard scheme that has emerged in recent years, and Internet Explorer, UC, Baidu and other browsers have not shown corresponding support for this, we need to solve the problem of compatibility on unsupported browsers through some schemes. So let’s start with Polyfill, which allows browsers to support most of ES Modules features. This Module is called ES Module Loader

This module is actually a JS file that we can import into the page as provided by Installation and Usage in Readme.

Babel-browser-build. js is a version file that Babel runs in the browser. Polyfill actually reads the code through the ES Module and gives it to Babel to convert to make the code work. It is worth mentioning that in IE, even introducing es Module loader in this way will not run the script properly, because IE does not support Promise yet, so we need to introduce Promise Polyfill specifically for IE: Promise Polyfill.

After we successfully introduce es Module Loader, our JS code will execute…. twice in browsers that support ES Module Just because the ES Module loader executes once, the browser itself executes once. This problem can be solved with the nomodule attribute of the script tag. The nomodule attribute allows our JS code to work only in browsers that do not support ES Modules, so we can add the nomodule attribute to the imported Polyfill script to solve the problem of repeated code execution, as shown in the following example:

<script nomodule src="polyfill.min.js"></script>
<script nomodule src="babel-browser-build.js"></script>
<script nomodule src="browser-es-module-loader.js"></script>
Copy the code

This compatibility is best left to play with in our development environment, because this dynamic conversion is performed at runtime, which can lead to particularly poor performance in production. So how do we make compatibility? We can convert ES Module code into code that works with compatible browsers when compiled directly before release to production, which we’ll discuss later when we use the Module packaging tool.

9. ES Modules in Node.js

ES Modules since it is the modular standard at the level of JavaScript language, it will unify the modular requirements of all JavaScript fields. As a very important field of JS, Node.js is also gradually supporting the features of ES Modules. Since Node 8.5, ES Modules have been supported internally in an experimental way:

However, we should not be too optimistic, ES Modules is still in a transitional state because there is a huge gap between node.js and CommonJs. I’ll start with two images. This is a Warning reported by using ES Modules in Node and an error reported by using some of the unsupported ES Modules features:

So, the way we use in Node.js is not to say more verbose, we understand that in the JS field, ES Modules modular scheme will be the river’s lake! Of course, if you’re still interested in learning about using ES Modules in the latest Node environment, stay tuned for my next article.

conclusion

The above content is only the beginning of the topic “Front-end modular Development”, which is biased towards understanding historical concepts and the use of basic syntax features. I will continue to post articles on front-end modular development in Webpack, Rollup, ESLint, and other normalization standards. I will post a link to the next article here, and welcome your continued attention