如何将传统的JS应用程序迁移到模块化系统

8

我有一个大约15k行代码的JS应用程序(即NetSuite应用程序),采用旧式全局方式编写。该应用程序由26个文件组成,它们之间的依赖关系完全不清楚。

目标是将应用程序优雅地重构为较小的模块。通过“优雅”,我的意思是在较长时间内不会破坏或锁定应用程序,而是在完成每个块后,应用程序仍然可用。

我在这里的想法是将我们现有的所有JS文件连接到单个文件束中。之后,可以将一些代码提取到模块中。遗留代码可以开始导入它。模块和导入应该使用webpack\whatever进行转译,而遗留代码仍然保持全局样式。最后,所有这些都打包到单个JS文件中并部署。

我的问题是

  1. 也许有更好的方法吗?这听起来像是一个典型的问题
  2. 是否有任何可用的工具支持我的方法?

我尝试了webpack,但我没有成功地得到我想要的结果。由于需要导入\导出的方法\变量数量,因此export-loaderresolve-loader不是选项。

例子

现在的代码看起来像

function someGlobalFunction() {
  ...
}

var myVar = 'something';
// and other 15k lines in 26 files like this

我理想中想要实现的是:
function define(...) { /* function to define a module */ }

function require(moduleName) { /* function to import a module */ }

// block with my refactored out module definitions

define('module1', function () {
  // extracted modularised code goes here
});

define('module2', function () {
  // extracted modularised code goes here
});

// further down goes legacy code, which can import new modules

var myModule = require('myNewModule');
function myGlobalLegacyFunction() {
   // use myModule
}

1
我很想听听你决定采取的方法。 - Bower
2
那其实从未发生过,所以我无法分享任何经验。我甚至无法制作出那个计划的原型来运行,但现在它似乎仍然是可行的。根据我的当前经验,Webpack应该能够涵盖模块的内容。 - scorpp
2个回答

3

我将遵循类似于这里概述的方法:https://zirho.github.io/2016/08/13/webpack-to-legacy/

简而言之:

假设您可以配置webpack将像下面这样的东西转换为

export function myFunction(){...}

将所有模块打包到一个名为bundle.js的文件中,该文件可以被浏览器识别。在webpack的入口点,您可以从模块中导入所有内容,并将其分配给window对象:
// using namespace import to get all exported things from the file
import * as Utils from './utils'

// injecting every function exported from utils.js into global scope(window)
Object.assign(window, Utils).

然后,在您的html中,确保在现有代码之前包含webpack输出:
<script type="text/javascript" src="bundle.js"></script>
<script type="text/javascript" src="legacy.js"></script>

当你将函数从legacy.js移动到myNiceModule.js时,你的IDE应该能够帮助识别该方法的客户端。检查一下是否仍然有客户端全局感知它-如果没有,那么它就不需要全局可用。


0
到目前为止还没有一个好的答案,希望提问者能回来。我敢挑战地说,这个问题是无法解决的。
所有模块技术最终都会破坏脚本在文档头中顺序执行的特性。
所有动态添加的脚本都是并行加载的,它们不等待彼此。由于您可以肯定几乎所有这样可怕的旧版 JavaScript 代码都依赖于连续执行,其中第二个脚本可能依赖于第一个之前的脚本,所以一旦您动态加载这些,它就会被打破。
如果您使用某些模块方法(包括ES7 2018模块或require.js 或者您自己的),则需要在回调或Promise / then 函数块中执行依赖于已发生加载的代码。这会破坏隐式全局上下文,因此我们在旧版JavaScript代码文件中发现的所有这些全局函数和变量的意大利面条般的卷曲将不再在全局范围内定义。
我已经确定只有两个技巧可以实现平滑过渡:
要么找到一种方法来暂停脚本块的继续,直到导入 Promise 解析完成。
要么使用异步模块定义(AMD)/ requireJS 式的依赖管理系统,其中重要的是确保所有这些模块都已经加载完成了,以避免在执行时出现错误。
const promise = require("dep1.js", "dep2.js", "dep3.js");
await promise;
// legacy stuff follows

或者有一种方法可以将函数内部块的作用域显式地恢复到全局作用域。

with(window) {
    function foo() { return 123; }
    var bar = 543;
}

但是 JavaScript 仙女没有满足任何一个愿望。

事实上,我读到即使 await 关键字本质上也只是将其余语句打包成函数,在 promise 解决时调用:

async function() {
    ... aaa makes promise ...
    await promise; 
    ... bbb ...
}

只是,我想,与众无异

async function() {
    ... aaa makes promise ...
    promise.then(r => {
        ... bbb ...
    });
}

这意味着,唯一修复此问题的方法是将旧版 JavaScript 静态放置在 head/script 元素中,并逐步将其移入模块,但仍然静态加载它们。

我正在尝试自己的模块样式:

(function(scope = {}) {
    var v1 = ...;
    function fn1() { ... }
    var v2 = ...;
    function fn2() { ... }

    return ['v1', 'fn1', 'v2', 'fn2']
           .reduce((r, n) => { 
                        r[n] = eval(n); 
                        return r; 
                    }, scope);
})(window)

通过使用 window 对象调用此“模块”函数,导出的项目将像传统代码一样放在其中。
我通过使用 knockout.js 并与源可读文件一起工作来获得很多信息,但是在这种模块函数调用中,最终所有功能都在“ko”对象上。
我不喜欢使用框架和“编译”,因此生成正确顺序的 HTML 标记序列以通过拓扑排序依赖树加载它们,虽然我可以快速编写这样的东西,但我不会这样做,因为我不想有任何“编译”步骤,甚至是我自己的。
更新:https://stackoverflow.com/a/33670019/7666635 给出了一个想法,即我们可以只需 Object.assign(window, module),这与我的技巧类似,将 window 对象传递到“模块”函数中。

很遗憾,我还没有完成那个迁移,所以我没有真正的经验可以分享。 关于你提到的点,我必须说模块并不等同于异步加载,而那个特定的项目也没有假设任何异步加载。 - scorpp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接