Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to manually implement a JavaScript Module Actuator

2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

Today, I will talk to you about how to manually implement a JavaScript module executor, many people may not know much about it. In order to make you understand better, the editor has summarized the following content for you. I hope you can get something according to this article.

What would you do if you were given a code snippet (dynamically acquired code string) that allows you to dynamically introduce the module at the front end and execute the functions in it?

Module.exports = {name: 'ConardLi', action: function () {console.log (this.name);}}; execution of the node environment

If we are in a node environment, we may quickly think of using the Module module, where there is a private function _ compile in the Module module that can load a module dynamically:

Export function getRuleFromString (code) {const myModule = new Module ('my-module'); myModule._compile (code,'my-module'); return myModule.exports;}

The implementation is that simple. Later we will review the principle of the _ compile function, but the requirements are not that simple. What if we want to introduce this code dynamically in the front-end environment?

Well, you heard right. Recently, we have encountered such a demand that we need to wipe out the logic of dynamically introducing modules at the front end and the Node end. OK, let's imitate the Module module to implement a JavaScript module executor in the front-end environment.

First, let's review the principle of module loading in node.

Principle of node Module module loading

Node.js follows the CommonJS specification, whose core idea is to allow modules to load other dependent modules synchronously through the require method, and then export the interfaces that need to be exposed through exports or module.exports. It is mainly a module form defined to solve the scope problem of JavaScript, which can be executed in each module's own namespace.

In each NodeJs module, we can get the module, exports, _ _ dirname, _ _ filename and require modules. And the execution scope of each module is isolated from each other and does not affect each other.

In fact, the core of the whole module system above is the _ compile method of the Module class. Let's directly look at the source code of _ compile:

Module.prototype._compile = function (content, filename) {/ / remove the Shebang code content = internalModule.stripShebang (content); / / 1. Create the wrapper function var wrapper = Module.wrap (content); / / 2. The encapsulated function code var compiledWrapper = vm.runInThisContext (wrapper, {filename: filename, lineOffset: 0, displayErrors: true}); var dirname = path.dirname (filename); var require = internalModule.makeRequireFunction (this); var depth = internalModule.requireDepth; / / 3. Run the encapsulated functions of the module and pass module, exports, _ _ dirname, _ _ filename, require var result = compiledWrapper.call (this.exports, this.exports, require, this, filename, dirname); return result;}

I divided the whole implementation process into three steps:

Create an encapsulated function

The first step is to call the wrapper function inside Module to encapsulate the original content of the module. Let's take a look at the implementation of the wrapper function:

Module.wrap = function (script) {return Module.wrapper [0] + script + Module.wrapper [1]; Module.wrapper = ['(function (exports, require, module, _ _ filename, _ _ dirname) {','\ n});']

The main purpose of CommonJS is to solve the scope problem of JavaScript so that each module can be executed in its own namespace. When there is no modular solution, we usually create a self-executing function to avoid variable contamination:

(function (global) {/ / execute code. }) (window)

So this step is crucial, first of all, the wrapper function wraps the code snippet of the module itself in a function scope and introduces the object we need as parameters. So the above block of code becomes:

(function (exports, require, module, _ _ filename, _ _ dirname) {module.exports = {name: 'ConardLi', action: function () {console.log (this.name);};})

Compile and encapsulate function code

The vm module in NodeJs provides a series of API for compiling and running code in a V8 virtual machine environment. JavaScript code can be compiled and run immediately, or compiled, saved, and then run.

Vm.runInThisContext () compiles and executes code in the context of the current global object, and finally returns the result. The running code cannot get the local scope, but can get the current global object.

Var compiledWrapper = vm.runInThisContext (wrapper, {filename: filename, lineOffset: 0, displayErrors: true})

So after the above code is executed, the code snippet string is compiled into a real executable function:

(function (exports, require, module, _ _ filename, _ _ dirname) {module.exports = {name: 'ConardLi', action: function () {console.log (this.name);};})

Run the encapsulated function

Finally, the compiled executable function is executed through call and the corresponding object is passed in.

Var result = compiledWrapper.call (this.exports, this.exports, require, this, filename, dirname)

So when you see here, you should understand that the module we get in the module is the instance of the Module module itself, and the exports we call directly is actually a reference to module.exports, so we can use either module.exports or exports to export a module.

Implement the Module module

If we want to execute a CommonJS module in the front-end environment, then we just need to implement a Module module manually and revisit the above process. If we only consider the logic dynamically introduced by the module code block, we can abstract the following code:

Export default class Module {exports = {} wrapper = ['return (function (exports, module) {','\ n});']; wrap (script) {return `${this.wrapper [0]} ${script} ${this.wrapper [1]}`; compile (content) {const wrapper = this.wrap (content); const compiledWrapper = vm.runInContext (wrapper) CompiledWrapper.call (this.exports, this.exports, this);}}

There is a problem here. There is no VM module in the browser environment. VM will load the code into a context and put it into a sandbox, so that the whole operation of the code will be executed in a closed context. We need to implement a sandbox in the browser environment.

Implement browser sandbox

Eval

When we execute a code snippet in a browser, the first thing we think of is eval. The eval function can execute a Javascript string as a code snippet.

However, code executed by eval () has access to closures and global scopes, which leads to a security hazard known as code injection code injection, which is easy to use but often abused and is one of the most notorious features of JavaScript.

As a result, many alternatives have emerged to execute string code values in the sandbox rather than in the global scope.

New Function ()

The Function constructor is an alternative to eval (). New Function (... args, 'funcBody') evaluates the incoming' funcBody' string and returns the function that executes the code.

Fn = new Function (. Args, 'functionBody')

The fn returned is a defined function, and the last argument is the function body. It differs from eval in two ways:

Fn is a piece of compiled code that can be executed directly, while eval needs to be compiled once

Fn does not have scope access to its closure, but it can still access the global scope

However, this still does not solve the problem of accessing the global scope.

With keyword

With is an unpopular keyword for JavaScript. It allows a half-sandbox operating environment. The code in the with code block will first try to get the variable from the incoming sandbox object, but if it is not found, it will look in the closure and global scope. Access to the closure scope can be avoided with new Function (), so we only need to deal with the global scope. The in operator is used internally in with. Each variable accessed in a block is judged using the variable in sandbox condition. If the condition is true, the variable is read from the sandbox object. Otherwise, it looks for variables in the global scope.

Function compileCode (src) {src = 'with (sandbox) {' + src +'} 'return new Function (' sandbox', src)}

Just imagine, if the variable in sandbox condition is always true, won't the sandboxed environment never read the environment variables? So we need to hijack the properties of the sandbox object so that all the properties can always be read.

Proxy

A Proxy function is provided in ES6, which is an interceptor before accessing the object. We can use Proxy to intercept the properties of sandbox so that all attributes can be read:

Function compileCode (code) {code = 'with (sandbox) {' + code +'}'; const fn = new Function ('sandbox', code); return (sandbox) = > {const proxy = new Proxy (sandbox, {has () {return true;}}); return fn (proxy);}}

Symbol.unscopables

Symbol.unscopables is a famous tag. A famous tag is a built-in JavaScript Symbol that can be used to represent internal language behaviors.

Symbol.unscopables defines the unscopable (unqualified) property of an object. In the with statement, you cannot retrieve the Unscopable property from the Sandbox object, but directly from the closure or global scope.

So we need to reinforce the situation of Symbol.unscopables.

Function compileCode (code) {code = 'with (sandbox) {' + code +'}'; const fn = new Function ('sandbox', code); return (sandbox) = > {const proxy = new Proxy (sandbox, {has () {return true;}, get (target, key, receiver) {if (key = = Symbol.unscopables) {return undefined } Reflect.get (target, key, receiver);}}); return fn (proxy);}}

Whitelist of global variables

However, at this time, the sandbox cannot execute the various utility classes and functions provided to us by default by the browser. It can only be used as a pure function without any side effects. When we want to use some global variables or classes, we can customize a whitelist:

Const ALLOW_LIST = ['console']; function compileCode (code) {code =' with (sandbox) {'+ code +'}'; const fn = new Function ('sandbox', code); return (sandbox) = > {const proxy = new Proxy (sandbox, {has () {if (! ALLOW_LIST.includes (key)) {return true }, get (target, key, receiver) {if (key = Symbol.unscopables) {return undefined;} Reflect.get (target, key, receiver);}}); return fn (proxy);}} final code:

All right, to sum up the above code, we have completed a simple JavaScript module executor:

Const ALLOW_LIST = ['console']; export default class Module {exports = {} wrapper = [' return (function (exports, module) {','\ n});']; wrap (script) {return `${this.wrapper [0]} ${script} ${this.wrapper [1]}`; runInContext (code) {code = `with (sandbox) {${code}} ` Const fn = new Function ('sandbox', code); return (sandbox) = > {const proxy = new Proxy (sandbox, {has (target, key) {if (! ALLOW_LIST.includes (key)) {return true;}}, get (target, key, receiver) {if (key = = Symbol.unscopables) {return undefined } Reflect.get (target, key, receiver);}}); return fn (proxy);}} compile (content) {const wrapper = this.wrap (content); const compiledWrapper = this.runInContext (wrapper) ({}); compiledWrapper.call (this.exports, this.exports, this);}}

Test execution effect:

Function getModuleFromString (code) {const scanModule = new Module (); scanModule.compile (code); return scanModule.exports;} const module = getModuleFromString (`module.exports = {name: 'ConardLi', action: function () {console.log (this.name);}; `); module.action (); / / ConardLi after reading the above, do you have any further understanding of how to manually implement a JavaScript module executor? If you want to know more knowledge or related content, please follow the industry information channel, thank you for your support.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report