Узловой стиль требуется для JavaScript в браузере?

85

Существуют ли какие-либо библиотеки для встроенного в браузер javascript, которые обеспечивают такую ​​же гибкость / модульность / простоту использования, что и Node require?

Для более подробной информации: причина requireв том, что она:

  1. Позволяет динамически загружать код из других мест (что, на мой взгляд, стилистически лучше, чем связывание всего вашего кода в HTML)
  2. Он обеспечивает последовательный интерфейс для создания модулей.
  3. Модули легко зависят от других модулей (поэтому я мог бы написать, например, API, который требует jQuery, чтобы я мог использовать jQuery.ajax()
  4. Загруженный javascript имеет ограниченную область видимости , что означает, что я могу загружать var dsp = require("dsp.js");и иметь доступ dsp.FFT, что не будет мешать работе моего локальногоvar FFT

Мне еще предстоит найти библиотеку, которая бы делала это эффективно. Я обычно использую следующие обходные пути:

  • coffeescript-concat - достаточно просто потребовать другие js, но вы должны его скомпилировать, а это значит, что он не так хорош для быстрой разработки (например, создание API-интерфейсов в тесте)

  • RequireJS - популярный, простой и решает вопросы 1-3, но отсутствие области видимости является серьезным препятствием (я считаю, что head.js аналогичен тем, что в нем отсутствует область видимости, хотя у меня никогда не было возможности его использовать. Точно так же LABjs может загружать и.wait() смягчать проблемы с зависимостями, но по-прежнему не выполняет определение объема)

Насколько я могу судить, существует множество решений для динамической и / или асинхронной загрузки javascript, но они, как правило, имеют те же проблемы с областью видимости, что и простая загрузка js из HTML. Больше всего на свете мне нужен способ загрузки javascript, который вообще не загрязняет глобальное пространство имен, но по-прежнему позволяет мне загружать и использовать библиотеки (как это делает node).

ОБНОВЛЕНИЕ 2020 ГОДА: модули теперь являются стандартными в ES6, и с середины 2020 года они изначально поддерживаются большинством браузеров. . Модули поддерживают как синхронную, так и асинхронную (с использованием Promise) загрузку. Моя текущая рекомендация заключается в том, что в большинстве новых проектов следует использовать модули ES6 и использовать транспилятор для возврата к одному файлу JS для устаревших браузеров.

Как правило, сегодня полоса пропускания намного шире, чем когда я изначально задавал этот вопрос. Так что на практике вы могли бы разумно выбрать всегда использовать транспилятор с модулями ES6 и сосредоточить свои усилия на эффективности кода, а не на сети.

ПРЕДЫДУЩИЙ РЕДАКТИРОВАНИЕ (или если вам не нравятся модули ES6): с момента написания этого я широко использовал RequireJS (который теперь имеет гораздо более четкую документацию). На мой взгляд, RequireJS был правильным выбором. Хочу пояснить, как работает система, для людей, которые так же запутались, как и я:

Можно использовать requireв повседневной разработке. Модуль может быть чем угодно, возвращаемым функцией (обычно объектом или функцией), и имеет область видимости как параметр. Вы также можете скомпилировать свой проект в один файл для развертывания r.js(на практике это почти всегда быстрее, даже если requireскрипты можно загружать параллельно).

Основное различие между RequireJS и типом node-style require, подобным использованию browserify (классный проект, предложенный tjameson), заключается в том, как модули проектируются и требуются:

  • RequireJS использует AMD (определение асинхронного модуля). В AMD requireпринимает список модулей (файлов javascript) для загрузки и функцию обратного вызова. Когда он загружает каждый из модулей, он вызывает обратный вызов с каждым модулем в качестве параметра для обратного вызова. Таким образом, он действительно асинхронный и поэтому хорошо подходит для Интернета.
  • Node использует CommonJS. В CommonJS requireэто блокирующий вызов, который загружает модуль и возвращает его как объект. Это отлично работает для Node, потому что файлы считываются из файловой системы, что достаточно быстро, но плохо работает в Интернете, поскольку синхронная загрузка файлов может занять намного больше времени.

На практике многие разработчики использовали Node (и, следовательно, CommonJS) еще до того, как увидели AMD. Кроме того, многие библиотеки / модули написаны для CommonJS (путем добавления элементов в exportsобъект), а не для AMD (путем возврата модуля из defineфункции). Поэтому многие веб-разработчики, перешедшие на Node, хотят использовать библиотеки CommonJS в Интернете. Это возможно, так как загрузка с <script>тега блокируется. Такие решения, как browserify, берут модули CommonJS (Node) и обертывают их, чтобы вы могли включать их в теги сценария.

Поэтому, если вы разрабатываете свой собственный многофайловый проект для Интернета, я настоятельно рекомендую RequireJS, поскольку это действительно модульная система для Интернета (хотя, честно говоря, я считаю AMD гораздо более естественной, чем CommonJS). В последнее время это различие стало менее важным, поскольку RequireJS теперь позволяет вам по существу использовать синтаксис CommonJS. Кроме того, RequireJS можно использовать для загрузки модулей AMD в Node (хотя я предпочитаю node-amd-loader ).

Алекс Черчилль
источник
1
Примечание. RequireJS действительно поддерживает модульность и может иметь область видимости. С тех пор, как я спросил, я использовал его более широко. На мой взгляд, он обладает множеством функциональных возможностей, но для его эффективного использования требуется много чтения документации, а также требуется некоторая форма первоклассной синхронной загрузки, прежде чем она станет идеальной.
Алекс Черчилль
1
Является ли асинхронность такой значимой? всякий раз, когда мне нужен код, я в основном не могу продолжить, потому что он определяет функции, и мне нужно что-то делать ...
Майкл

Ответы:

17

Проверьте Эндер . Он делает много этого.

Кроме того, браузер работает очень хорошо. Я использовал require-kiss ¹, и он работает. Наверное, есть и другие.

Я не уверен насчет RequireJS. Это просто не то же самое, что и узел. У вас могут возникнуть проблемы с загрузкой из других мест, но это может сработать. Пока есть метод provide или что-то, что можно вызвать.

TL; DR - я бы порекомендовал просмотреть или потребовать поцелуй.


Обновить:

1: require-kiss теперь мертв, и автор удалил его. С тех пор я без проблем использую RequireJS. Автор требует поцелуя написал pakmanager и Pakman . Полное раскрытие, работаю с разработчиком.

Лично мне больше нравится RequireJS. Его намного проще отлаживать (вы можете иметь отдельные файлы в разработке и один развернутый файл в производстве) и построен на твердом «стандарте».

beatgammit
источник
Ссылка на require-kiss вроде мертва. Простой (пере) поиск ни к чему не привел - куда он делся?
Joel Purra
@JoelPurra - require-kiss был удален и заменен pakmanager. Я рекомендую require-js сейчас. Я обновил ответ.
beatgammit
хороший ответ, чувак :), не могли бы вы проверить вопрос, который я только что задал, который похож на этот (но в то же время отличается)? stackoverflow.com/questions/43237875/…
Webeng 05
16

Я написал небольшой скрипт, который позволяет асинхронно и синхронно загружать файлы Javascript, которые могут быть здесь полезны. Он не имеет зависимостей и совместим с Node.js и CommonJS. Установка довольно проста:

$ npm install --save @tarp/require

Затем просто добавьте следующие строки в свой HTML, чтобы загрузить основной модуль:

<script src="/node_modules/@tarp/require/require.min.js"></script>
<script>Tarp.require({main: "./scripts/main"});</script>

Внутри вашего основного модуля (и, конечно, любого подмодуля) вы можете использовать то, require()что вы знаете из CommonJS / NodeJS. Полную документацию и код можно найти на GitHub .

Торбен
источник
как вы используете функцию из main.js? Например main.js имеет простой myFunctionв alert("hello"). Я звоню main.myFunction()? Это не сработает?
Брайан Вайли
Вам нужно использовать, Tarp.require({ expose: true });чтобы он работал? Как в вашем тесте?
Брайан Вайли
10

Я понимаю, что могут быть новички, желающие организовать свой код. Это 2020 год , и если вы рассматриваете модульное приложение JS, вам следует начать работу с npm и Webpack прямо сейчас.

Вот несколько простых шагов, чтобы начать работу:

  1. В корне вашего проекта запустите, npm init -yчтобы инициализировать проект npm
  2. Загрузите сборщик модуля Webpack: npm install webpack webpack-cli
  3. Создайте файл index.html:
<!DOCTYPE html>
<html lang="en">
<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>App</title>
</head>
<body>

    <script src="_bundle.js"></script>
</body>
</html>

Обратите особое внимание на _bundle.jsфайл - это будет последний JS-файл, сгенерированный webpack, вы не будете изменять его напрямую (продолжайте читать).

  1. Создайте, <project-root>/app.jsв котором вы будете импортировать другие модули:
const printHello = require('./print-hello');

printHello();
  1. Создайте образец print-hello.jsмодуля:
module.exports = function() {
    console.log('Hello World!');
}
  1. Создайте <project-root>/webpack.config.jsи скопируйте следующее:
var path = require('path');

module.exports = {
  entry: './app.js',
  output: {
    path: path.resolve(__dirname),
    filename: '_bundle.js'
  }
};

В приведенном выше коде есть 2 пункта:

  • entry app.js- это место, где вы будете писать свой JS-код. Он импортирует другие модули, как показано выше.
  • output _bundle.js- это ваш последний пакет, сгенерированный webpack. Это то, что ваш html увидит в конце.

-7. Откройте свой package.jsи замените scriptsследующей командой:

  "scripts": {
    "start": "webpack --mode production -w"
  },
  1. И , наконец , запустить часы сценарий app.jsи генерировать _bundle.jsфайл, выполнив: npm start.
  2. Наслаждайтесь кодированием!
Ильяс Ассаинов
источник
1
в 2020 году это должно быть отмечено как правильный ответ
p13rnd
9

Вариант отличного ответа Ильи Харламова с некоторым кодом, чтобы он хорошо работал с инструментами разработчика Chrome.

//
///- REQUIRE FN
// equivalent to require from node.js
function require(url){
    if (url.toLowerCase().substr(-3)!=='.js') url+='.js'; // to allow loading without js suffix;
    if (!require.cache) require.cache=[]; //init cache
    var exports=require.cache[url]; //get from cache
    if (!exports) { //not cached
            try {
                exports={};
                var X=new XMLHttpRequest();
                X.open("GET", url, 0); // sync
                X.send();
                if (X.status && X.status !== 200)  throw new Error(X.statusText);
                var source = X.responseText;
                // fix (if saved form for Chrome Dev Tools)
                if (source.substr(0,10)==="(function("){ 
                    var moduleStart = source.indexOf('{');
                    var moduleEnd = source.lastIndexOf('})');
                    var CDTcomment = source.indexOf('//@ ');
                    if (CDTcomment>-1 && CDTcomment<moduleStart+6) moduleStart = source.indexOf('\n',CDTcomment);
                    source = source.slice(moduleStart+1,moduleEnd-1); 
                } 
                // fix, add comment to show source on Chrome Dev Tools
                source="//@ sourceURL="+window.location.origin+url+"\n" + source;
                //------
                var module = { id: url, uri: url, exports:exports }; //according to node.js modules 
                var anonFn = new Function("require", "exports", "module", source); //create a Fn with module code, and 3 params: require, exports & module
                anonFn(require, exports, module); // call the Fn, Execute the module
                require.cache[url]  = exports = module.exports; //cache obj exported by module
            } catch (err) {
                throw new Error("Error loading module "+url+": "+err);
            }
    }
    return exports; //require returns object exported by module
}
///- END REQUIRE FN
Лусио М. Тато
источник
Спасибо Лючио! Я давно искал такое минимальное решение. Я расширил его для поддержки относительных путей. Смотри ниже.
Trausti Kristjansson
5
(function () {
    // c is cache, the rest are the constants
    var c = {},s="status",t="Text",e="exports",E="Error",r="require",m="module",S=" ",w=window;
    w[r]=function R(url) {
        url+=/.js$/i.test(url) ? "" : ".js";// to allow loading without js suffix;
        var X=new XMLHttpRequest(),module = { id: url, uri: url }; //according to the modules 1.1 standard
        if (!c[url])
            try {
                X.open("GET", url, 0); // sync
                X.send();
                if (X[s] && X[s] != 200) 
                    throw X[s+t];
                Function(r, e, m, X['response'+t])(R, c[url]={}, module); // Execute the module
                module[e] && (c[url]=module[e]);
            } catch (x) {
                throw w[E](E+" in "+r+": Can't load "+m+S+url+":"+S+x);
            }
        return c[url];
    }
})();

Лучше не использовать в производстве из-за блокировки. (В node.js require () - это хорошо блокирующий вызов).

Илья Харламов
источник
не должно быть "exports: {}" свойством "module"? и вызов be (R, module.exports, module)
Лючио М. Тато
@ LucioM.Tato Я не уверен, не вижу упоминания module.exports в стандарте Modules 1.1 . Вы всегда можете позвонить по запросу require (module.id), чтобы получить экспорт
Илья Харламов
Да. Вы правы, я думал о реализации модулей node.js. Я добавил некоторые изменения в код в вашем действительно хорошем ответе, чтобы он хорошо работал с инструментами разработчика Chrome (я использую его как IDE времени отладки). Я отправлю код как еще один ответ на этот вопрос, если он будет кому-то полезен.
Lucio M. Tato
1

Require-stub - обеспечивает соответствие узлов requireв браузере, разрешает как модули, так и относительные пути. Использует технику, аналогичную TKRequire (XMLHttpRequest). Результирующий код полностью require-stubдоступен для просмотра и может служить заменой watchify.

dy_
источник
0

Вот расширение фантастического ответа Лучио М. Тато, которое позволяет рекурсивно загружать модули с относительными путями.

Вот проект github для размещения решения и пример его использования:

https://github.com/trausti/TKRequire.js

Чтобы использовать TKRequire.js, включите следующую строку в свой заголовок

<script type = "text / javascript" src = "./ TKRequire.js"> </script>

Затем загрузите модули, как в node.js:

var MyModule = require ("./ относительный / путь / к / MyModule.js");

Траусти Кристьянссон
источник
Спасибо, Траусти. Если вы используете javascript, вам следует проверить github.com/luciotato/LiteScript (бета). PD: На каком уровне CC Saga вы играете сейчас? : P
Лусио М. Тато
ссылки мертвы, как мы можем взять ваш код и взглянуть на него?
П.А.