Использование модуля общего узла для общих классов

15

Цель

Итак, у меня есть проект с этой структурой:

  • ионно-приложение
  • firebase-функции
  • общий

Цель состоит в том, чтобы определить общие интерфейсы и классы в sharedмодуле.

ограничения

Я не хочу загружать свой код в npm, чтобы использовать его локально, и вообще не планирую загружать код. Он должен на 100% работать в автономном режиме.

Хотя процесс развития должен работать в автономном режиме, ionic-appи firebase-functionsмодули будут развернуты firebase (хостинг и функция). Поэтому код из sharedмодуля должен быть доступен там.

Что я пробовал до сих пор

  • Я пытался использовать Проект Ссылки в машинописном тексте, но не приблизился к работе
  • Я пытался установить его как модуль npm, как во втором ответе на этот вопрос.
    • Вначале кажется, что он работает нормально, но во время сборки я получаю такую ​​ошибку при запуске firebase deploy:
Function failed on loading user code. Error message: Code in file lib/index.js can't be loaded.
Did you list all required modules in the package.json dependencies?
Detailed stack trace: Error: Cannot find module 'shared'
    at Function.Module._resolveFilename (module.js:548:15)
    at Function.Module._load (module.js:475:25)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/srv/lib/index.js:5:18)

Вопрос

Есть ли у вас решение для создания общего модуля, используя либо конфигурацию сценариев, либо NPM?

Пожалуйста, не отмечайте это как дубликат → Я попробовал любое решение, которое я нашел на StackOverflow.

Дополнительная информация

Конфиг для общего доступа:

// package.json
{
  "name": "shared",
  "version": "1.0.0",
  "description": "",
  "main": "dist/src/index.js",
  "types": "dist/src/index.d.ts",
  "files": [
    "dist/src/**/*"
  ],
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "publishConfig": {
    "access": "private"
  }
}

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "rootDir": ".",
    "sourceRoot": "src",
    "outDir": "dist",
    "sourceMap": true,
    "declaration": true,
    "target": "es2017"
  }
}

Конфиг для функций:

// package.json
{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "8"
  },
  "main": "lib/index.js",
  "dependencies": {
    "firebase-admin": "^8.0.0",
    "firebase-functions": "^3.1.0",
    "shared": "file:../../shared"
  },
  "devDependencies": {
    "@types/braintree": "^2.20.0",
    "tslint": "^5.12.0",
    "typescript": "^3.2.2"
  },
  "private": true
}


// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./",
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": false,
    "rootDir": "src",
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017"
  }
}

Текущее мнение

Я добавил скрипт npm в общий модуль, который копирует все файлы (без index.js) в другие модули. Проблема в том, что я проверяю дублирующий код в SCM, и мне нужно запускать эту команду при каждом изменении. Кроме того, IDE просто обрабатывает его как разные файлы.

MauriceNino
источник

Ответы:

4

Предисловие: я не слишком знаком с тем, как работает компиляция Typescript и как package.jsonв таком модуле нужно определить. Хотя это решение и работает, его можно считать хакерским способом решения поставленной задачи.

Предполагая следующую структуру каталогов:

project/
  ionic-app/
    package.json
  functions/
    src/
      index.ts
    lib/
      index.js
    package.json
  shared/
    src/
      shared.ts
    lib/
      shared.js
    package.json

При развертывании службы Firebase вы можете прикрепить команды к перехватчикам preploy и postdeploy . Это делается firebase.jsonчерез свойства predeployи postdeployна желаемом сервисе. Эти свойства содержат массив последовательных команд для запуска до и после развертывания вашего кода соответственно. Кроме того, эти команды вызываются с переменными среды RESOURCE_DIR(путь к каталогу ./functionsили ./ionic-app, в зависимости от того, что применимо) иPROJECT_DIR (путь к каталогу, содержащийfirebase.json ).

Используя predeployмассив functionsизнутри firebase.json, мы можем скопировать код общей библиотеки в папку, развернутую в экземпляре Cloud Functions. Делая это, вы можете просто включить общий код , как если бы она была библиотекой , расположенной в папке или вы можете сопоставить это имя с помощью отображения пути машинописи в в tsconfig.jsonпоименованном модуль (так что вы можете использоватьimport { hiThere } from 'shared'; ).

Определение predeployхука (использует глобальную установку shxдля совместимости с Windows):

// firebase.json
{
  "functions": {
    "predeploy": [
      "shx rm -rf \"$RESOURCE_DIR/src/shared\"", // delete existing files
      "shx cp -R \"$PROJECT_DIR/shared/.\" \"$RESOURCE_DIR/src/shared\"", // copy latest version
      "npm --prefix \"$RESOURCE_DIR\" run lint", // lint & compile
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  },
  "hosting": {
    "public": "ionic-app",
    ...
  }
}

Связывание источника машинописной копии скопированной библиотеки с конфигурацией компилятора машинописи функций:

// functions/tsconfig.json
{
  "compilerOptions": {
    ...,
    "baseUrl": "./src",
    "paths": {
      "shared": ["shared/src"]
    }
  },
  "include": [
    "src"
  ],
  ...
}

Связывание имени модуля «shared» с папкой пакета скопированной библиотеки.

// functions/package.json
{
  "name": "functions",
  "scripts": {
    ...
  },
  "engines": {
    "node": "8"
  },
  "main": "lib/index.js",
  "dependencies": {
    "firebase-admin": "^8.6.0",
    "firebase-functions": "^3.3.0",
    "shared": "file:./src/shared",
    ...
  },
  "devDependencies": {
    "tslint": "^5.12.0",
    "typescript": "^3.2.2",
    "firebase-functions-test": "^0.1.6"
  },
  "private": true
}

Тот же подход можно использовать с папкой хостинга.


Надеемся, что это вдохновит того, кто более знаком с компиляцией Typescript, найти более чистое решение, которое использует эти хуки.

samthecodingman
источник
3

Возможно, вы захотите попробовать Lerna , инструмент для управления проектами JavaScript (и TypeScript) с несколькими пакетами.

Настроить

Предполагая, что ваш проект имеет следующую структуру каталогов:

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json

Обязательно укажите правильный уровень доступа ( privateи config/accessключи) во всех модулях, которые вы не хотите публиковать, а также typingsзапись в вашем sharedмодуле:

Общий:

{
  "name": "shared",
  "version": "1.0.0",
  "private": true,
  "config": {
    "access": "private"
  },
  "main": "lib/index.js",
  "typings": "lib/index.d.ts",
  "scripts": {
    "compile": "tsc --project tsconfig.json"
  }
}

Ионные-приложение:

{
  "name": "ionic-app",
  "version": "1.0.0",
  "private": true,
  "config": {
    "access": "private"
  },
  "main": "lib/index.js",
  "scripts": {
    "compile": "tsc --project tsconfig.json"
  },
  "dependencies": {
    "shared": "1.0.0"
  }
}

С учетом вышеуказанных изменений вы можете создать корневой уровень, на package.jsonкотором вы можете указать любой, к devDependenciesкоторому у вас есть доступ ко всем модулям вашего проекта, например, инфраструктура модульного тестирования, tslint и т. Д.

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json
package.json         // root-level, same as the `packages` dir

Вы также можете использовать этот корневой уровень package.jsonдля определения скриптов npm, которые будут вызывать соответствующие скрипты в модулях вашего проекта (через lerna):

{
  "name": "my-project",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "compile": "lerna run compile --stream",
    "postinstall": "lerna bootstrap",
  },
  "devDependencies": {
    "lerna": "^3.18.4",
    "tslint": "^5.20.1",
    "typescript": "^3.7.2"
  },
}

После этого добавьте файл конфигурации lerna в корневой каталог:

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json
package.json
lerna.json

со следующим содержанием:

{
  "lerna": "3.18.4",
  "loglevel": "info",
  "packages": [
    "packages/*"
  ],
  "version": "1.0.0"
}

Теперь при запуске npm installв корневом каталоге, то postinstallсценарий определяется в корневом уровне package.jsonбудет вызывать lerna bootstrap.

Что lerna bootstrapозначает, что он будет символически связывать ваш sharedмодуль ionic-app/node_modules/sharedи firebase-functions/node_modules/shared, таким образом, с точки зрения этих двух модулей sharedвыглядит так же, как любой другой модуль npm.

компиляция

Конечно, символических ссылок недостаточно, так как вам все равно нужно скомпилировать их из TypeScript в JavaScript.

Вот где корневой уровень package.json compile сценарий вступает в игру.

Когда вы запустите npm run compileв корне вашего проекта, npm вызовет lerna run compile --streamи вызовет lerna run compile --streamскрипт, вызываемый compileв каждом из ваших модулей.package.json файле .

Поскольку каждый из ваших модулей теперь имеет свой собственный compileскрипт, вы должны иметьtsonfig.json файл для каждого модуля. Если вам не нравится дублирование, вы можете использовать tsconfig корневого уровня или комбинацию файлов tsconfig корневого уровня и tsconfig корневого уровня, унаследованных от корневого.

Если вы хотите посмотреть, как эта установка работает в реальном проекте, взгляните на Serenity / JS где я довольно широко ее использую.

развертывание

Хорошая вещь, sharedсвязанная с тем, что модуль имеет ссылки под node_modulesunder firebase-functionsи ionic-appyour devDepedenciesunder node_modulesunder root, заключается в том, что если вам нужно развернуть потребительский модуль где-нибудь ( ionic-appнапример, в этом), вы можете просто сжать все вместе вместе с ним node_modulesи не беспокоиться о необходимость удалить зависимости dev перед развертыванием.

Надеюсь это поможет!

январь

Ян Молак
источник
Интересная! Я обязательно проверю и посмотрю, подходит ли это.
MauriceNino
2

Другое возможное решение, если вы используете git для управления вашим кодом, это использование git submodule. С помощьюgit submodule вы можете включить в свой проект другой репозиторий git.

Применяется к вашему варианту использования:

  1. Выдвиньте текущую версию вашего shared-git-repository
  2. Используйте git submodule add <shared-git-repository-link>внутри вашего основного проекта (ов), чтобы связать общий репозиторий.

Вот ссылка на документацию: https://git-scm.com/docs/git-submodule

friedow
источник
На самом деле это неплохая идея, но местная разработка и тестирование в основном прошли с таким подходом.
MauriceNino
0

Если я правильно понимаю вашу проблему, решение является более сложным, чем один ответ, и оно частично зависит от ваших предпочтений.

Подход 1: Локальные копии

Вы можете использовать Gulp для автоматизации уже описанного вами рабочего решения, но IMO не очень прост в обслуживании и значительно увеличивает сложность, если в какой-то момент заходит другой разработчик.

Подход 2: Монорепо

Вы можете создать один репозиторий, содержащий все три папки, и соединить их так, чтобы они вели себя как один проект. Как уже ответили выше, вы можете использовать Lerna . Это требует некоторой настройки, но после этого эти папки будут работать как один проект.

Подход 3: Компоненты

Рассматривайте каждую из этих папок как отдельный компонент. Посмотрите на Бит . Это позволит вам настроить папки как меньшие части большого проекта, и вы сможете создать личную учетную запись, которая будет охватывать эти компоненты только для вас. После первоначальной настройки это позволит вам даже применять обновления к отдельным папкам, а родительская, которая их использует, автоматически получит обновления.

Подход 4: пакеты

Вы специально сказали, что не хотите использовать npm, но я хочу поделиться им, потому что в настоящее время я работаю с настройкой, как описано ниже, и отлично справляюсь со мной:

  1. Используйте npmилиyarn чтобы создать пакет для каждой папки (вы можете создать пакеты с областью действия для них обоих, чтобы код был доступен только вам, если это вас интересует).
  2. В родительской папке (которая использует все эти папки) созданные пакеты связаны как зависимости.
  3. Я использую webpack для связывания всего кода, используя псевдонимы пути webpack в сочетании с путями машинописи.

Работает как чудо, и когда пакеты являются символическими ссылками для локальной разработки, они работают полностью автономно, и, по моему опыту, каждая папка масштабируется отдельно и очень проста в обслуживании.

Запись

В моем случае «дочерние» пакеты уже предварительно скомпилированы, так как они довольно большие, и я создал отдельные tsconfigs для каждого пакета, но прекрасно то, что вы можете легко его изменить. В прошлом я использовал как машинный текст в модуле, так и скомпилированные файлы, а также необработанные js-файлы, так что все это очень и очень универсально.

Надеюсь это поможет

***** ОБНОВЛЕНИЕ **** Для продолжения по пункту 4: я прошу прощения, мой плохой. Может быть, я ошибся, потому что, насколько я знаю, вы не можете символическую ссылку на модуль, если он не загружен. Тем не менее, вот оно:

  1. У вас есть отдельный модуль npm, давайте использовать firebase-functions для этого . Вы компилируете его или используете raw ts, в зависимости от ваших предпочтений.
  2. В ваш родительский проект добавьте firebase-functions в качестве зависимости.
  3. В tsconfig.json, добавить"paths": {"firebase-functions: ['node_modules/firebase-functions']"}
  4. В веб-пакете - resolve: {extensions: ['ts', 'js'], alias: 'firebase-functions': }

Таким образом, вы ссылаетесь на все свои экспортируемые функции из firebase-functionsмодуля просто с помощью import { Something } from 'firebase-functions'. Webpack и TypeScript свяжут его с папкой модулей узла. При такой конфигурации родительский проект не будет заботиться оfirebase-functions том, написан модуль на TypeScript или ванильном javascript.

После настройки он отлично подойдет для производства. Затем, чтобы связать и работать в автономном режиме:

  1. Перейдите к firebase-functionsпроекту и написатьnpm link . Он создаст символическую ссылку, локальную для вашего компьютера, и отобразит ссылку, имя которой вы указали в package.json.
  2. Перейдите к родительскому проекту и напишите npm link firebase-functions, что создаст символическую ссылку и отобразит зависимость firebase-функций от папки, в которой вы ее создали.
Иван Джуров
источник
Я думаю, вы что-то не так поняли. Я никогда не говорил, что не хочу использовать npm. Фактически все три из этих модулей являются модулями узла. Я только что сказал, что не хочу загружать свои модули в npm. Не могли бы вы подробнее остановиться на этой четвертой части - это звучит интересно? может предоставить пример кода?
MauriceNino
Я добавлю еще один ответ, так как он будет длинным и нечитаемым в качестве комментария
Иван Джуров
Обновил мой первоначальный ответ, надеюсь, он понятнее
Иван Джуров
0

Я не хочу загружать свой код в npm, чтобы использовать его локально, и вообще не планирую загружать код. Он должен на 100% работать в автономном режиме.

Все модули npm устанавливаются локально и всегда работают в автономном режиме, но если вы не хотите публиковать свои пакеты публично, чтобы люди могли их видеть, вы можете установить частный реестр npm.

ProGet - это сервер частного репозитория NuGet / Npm, доступный для окон, который вы можете использовать в своей частной среде разработки / производства для размещения, доступа и публикации ваших личных пакетов. Хотя это на окнах, но я уверен, что есть разные альтернативы, доступные на Linux.

  1. Git Submodules - плохая идея, это действительно старый способ обмена кодом, который не является версионным, как пакеты, изменение и фиксация подмодулей - настоящая боль.
  2. Папка с исходным импортом также является плохой идеей, опять-таки проблема с версионностью, потому что если кто-то изменяет зависимую папку в зависимом хранилище, повторное отслеживание - это кошмар.
  3. Любые сторонние скриптовые инструменты для эмуляции разделения пакетов - пустая трата времени, так как npm уже предоставляет набор инструментов для управления пакетами.

Вот наш сценарий сборки / развертывания.

  1. У каждого частного пакета есть .npmrcчто registry=https://private-npm-repository.
  2. Мы публикуем все наши частные пакеты в нашем частном репозитории ProGet.
  3. Каждый приватный пакет содержит зависимые приватные пакеты на ProGet.
  4. Наш сервер сборки получает доступ к ProGet через установленную нами аутентификацию npm. Никто за пределами нашей сети не имеет доступа к этому хранилищу.
  5. Наш сервер сборки создает пакет npm, в bundled dependenciesкотором содержатся все пакеты внутри, node_modulesи производственному серверу никогда не требуется доступ к NPM или частным пакетам NPM, поскольку все необходимые пакеты уже связаны.

Использование частного репозитория npm имеет различные преимущества,

  1. Нет необходимости в собственном скрипте
  2. Подходит для конвейера сборки / публикации узлов
  3. Каждый частный пакет npm будет содержать прямую ссылку на ваш личный источник контроля git, легко отлаживать и расследовать ошибки в будущем
  4. Каждый пакет является моментальным снимком только для чтения, поэтому однажды опубликованная версия не может быть изменена, и пока вы создаете новые функции, существующая база кода с более старой версией зависимых пакетов не будет затронута.
  5. Вы можете легко сделать некоторые пакеты общедоступными и в будущем перейти к другому хранилищу.
  6. Если программное обеспечение вашего частного провайдера npm изменится, например, вы решите переместить свой код в частное облако реестра пакетов npm узла, вам не нужно будет вносить какие-либо изменения в ваш код.
Акаш Кава
источник
Это может быть решением, но, к сожалению, это не для меня. Спасибо за ваше время, хотя!
MauriceNino
Существует также локальный репозиторий npm, который устанавливается как сервер для небольших узлов, verdaccio.org
Akash Kava
-1

Инструмент, который вы ищете, это npm link. npm linkпредоставляет символические ссылки на локальный пакет npm. Таким образом, вы можете связать пакет и использовать его в своем основном проекте, не публикуя его в библиотеке пакетов npm.

Применяется к вашему варианту использования:

  1. Используйте npm linkвнутри вашей sharedупаковки. Это установит назначение символической ссылки для будущих установок.
  2. Перейдите к вашему основному проекту (ам). Внутри вашего functionsпакета и используйте, npm link sharedчтобы связать общий пакет и добавить его в node_modulesкаталог.

Вот ссылка на документацию: https://docs.npmjs.com/cli/link.html

friedow
источник
Насколько я знаю, ссылка npm предназначена только для тестирования и не работает, если вы хотите развернуть полученный код (например, мои функции).
MauriceNino
Я вижу, вам следует добавить это требование в ваш вопрос.
Фридоу
Это уже упоминалось в вопросе, но я уточню это.
MauriceNino