From 7824568ddd2f4d3d81d5b7308da34fd38ae34ae0 Mon Sep 17 00:00:00 2001 From: Trygve Lie <173003+trygve-lie@users.noreply.github.com> Date: Mon, 10 May 2021 09:52:28 +0200 Subject: [PATCH] feat: Convert to ESM (#286) * feat: Convert to ESM BREAKING CHANGE: Convert from CommonJS module to ESM * fix: Set type to be module Co-authored-by: Trygve Lie --- .eslintignore | 6 +- .eslintrc | 28 +++---- .github/workflows/publish.yml | 4 +- .github/workflows/test.yml | 2 +- .gitignore | 3 +- dist/package.json | 3 + lib/layout.js | 38 +++++----- package.json | 38 ++++++---- release.config.js => release.config.cjs | 0 rollup.config.js | 25 +++++++ tests/layout.js | 97 +++++++++++++------------ 11 files changed, 146 insertions(+), 98 deletions(-) create mode 100644 dist/package.json rename release.config.js => release.config.cjs (100%) create mode 100644 rollup.config.js diff --git a/.eslintignore b/.eslintignore index 94c2e315..91e6ca73 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,5 @@ +tap-snapshots/ coverage/ -coverage -tap-snapshots \ No newline at end of file +benchmark/ +example/ +dist/ \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 3c18e5fc..5b2cd9ee 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,18 +1,20 @@ { - "root": true, - "extends": ["airbnb-base", "prettier"], - "overrides": [ - { - "files": "__tests__/**/*", - "env": { - "jest": true - } - } - ], - "plugins": ["prettier"], + "extends": ["airbnb-base", "prettier"], + "plugins": ["prettier"], + "parser": "babel-eslint", + "parserOptions": { + "ecmaVersion": 11, + "sourceType": "module" + }, "rules": { - "strict": [0, "global"], + "import/prefer-default-export": "off", "class-methods-use-this": [0], - "no-param-reassign": [0] + "lines-between-class-members": [0], + "import/extensions": ["error", { + "js": "ignorePackages" + } + ] } } + + diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a8464641..d58d1b24 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: npm install run: | npm install @@ -38,7 +38,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: npm install run: | npm install diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e7b22ed..cfd46235 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - node-version: [12.x, 14.x] + node-version: [12.x, 14.x, 16.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} diff --git a/.gitignore b/.gitignore index 89511099..a681964e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ node_modules/**/* tmp/**/* .idea .idea/**/* -coverage \ No newline at end of file +coverage +dist \ No newline at end of file diff --git a/dist/package.json b/dist/package.json new file mode 100644 index 00000000..6a0d2ef2 --- /dev/null +++ b/dist/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} \ No newline at end of file diff --git a/lib/layout.js b/lib/layout.js index df40054c..1dc51533 100644 --- a/lib/layout.js +++ b/lib/layout.js @@ -1,10 +1,9 @@ /* eslint-disable consistent-return */ /* eslint-disable no-underscore-dangle */ /* eslint-disable no-restricted-syntax */ +/* eslint-disable no-param-reassign */ -'use strict'; - -const { +import { HttpIncoming, template, isFunction, @@ -12,23 +11,28 @@ const { uriIsRelative, AssetCss, AssetJs, -} = require('@podium/utils'); -const { validate } = require('@podium/schemas'); -const Context = require('@podium/context'); -const Metrics = require('@metrics/client'); -const objobj = require('objobj'); -const Client = require('@podium/client'); -const abslog = require('abslog'); -const Proxy = require('@podium/proxy'); -const pkg = require('../package.json'); +} from '@podium/utils'; +import * as schema from '@podium/schemas'; +import Context from '@podium/context'; +import Metrics from '@metrics/client'; +import objobj from 'objobj'; +import Client from '@podium/client'; +import abslog from 'abslog'; +import Proxy from '@podium/proxy'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; + +const currentDirectory = dirname(fileURLToPath(import.meta.url)); +const pkgJson = fs.readFileSync(join(currentDirectory, '../package.json'), 'utf-8'); +const pkg = JSON.parse(pkgJson); const _pathname = Symbol('_pathname'); const _sanitize = Symbol('_sanitize'); const _addCssAsset = Symbol('_addCssAsset'); const _addJsAsset = Symbol('_addJsAsset'); -const PodiumLayout = class PodiumLayout { - /* istanbul ignore next */ +export default class PodiumLayout { constructor({ name = '', pathname = '', @@ -37,12 +41,12 @@ const PodiumLayout = class PodiumLayout { client = {}, proxy = {}, } = {}) { - if (validate.name(name).error) + if (schema.name(name).error) throw new Error( `The value, "${name}", for the required argument "name" on the Layout constructor is not defined or not valid.`, ); - if (validate.uri(pathname).error) + if (schema.uri(pathname).error) throw new Error( `The value, "${pathname}", for the required argument "pathname" on the Layout constructor is not defined or not valid.`, ); @@ -249,5 +253,3 @@ const PodiumLayout = class PodiumLayout { return uri; } }; - -module.exports = PodiumLayout; diff --git a/package.json b/package.json index a1dfbb38..f54e69d2 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "@podium/layout", "version": "5.0.0-next.5", + "type": "module", "description": "Module for composing full page layouts out of page fragments in a micro frontend architecture.", - "main": "lib/layout.js", "license": "MIT", "keywords": [ "micro services", @@ -24,39 +24,46 @@ "index.d.ts", "README.md", "LICENSE", + "dist", "lib" ], + "main": "./dist/layout.js", + "exports": { + "require": "./dist/layout.js", + "import": "./lib/layout.js" + }, "types": "index.d.ts", "scripts": { "lint": "eslint .", "lint:fix": "eslint --fix .", "test": "tap --no-check-coverage", "test:snapshots": "tap --snapshot --no-check-coverage", - "lint:format": "eslint --fix .", - "precommit": "lint-staged" + "prepare": "npm run -s build", + "build": "rollup -c" }, "dependencies": { "@metrics/client": "2.5.0", - "@podium/client": "5.0.0-next.4", - "@podium/context": "5.0.0-next.3", - "@podium/proxy": "5.0.0-next.2", - "@podium/schemas": "5.0.0-next.1", - "@podium/utils": "5.0.0-next.2", + "@podium/client": "5.0.0-next.7", + "@podium/context": "5.0.0-next.5", + "@podium/proxy": "5.0.0-next.5", + "@podium/schemas": "5.0.0-next.4", + "@podium/utils": "5.0.0-next.6", "abslog": "2.4.0", + "ajv": "8.3.0", "lodash.merge": "4.6.2", - "objobj": "1.0.0", - "ajv": "8.3.0" + "objobj": "1.0.0" }, "devDependencies": { - "@podium/podlet": "4.4.21", + "@podium/podlet": "5.0.0-next.4", "@podium/test-utils": "2.3.0", "@semantic-release/changelog": "5.0.1", "@semantic-release/commit-analyzer": "8.0.1", "@semantic-release/git": "9.0.0", - "@semantic-release/github": "7.2.1", - "@semantic-release/npm": "7.1.1", + "@semantic-release/github": "7.2.3", + "@semantic-release/npm": "7.1.3", "@semantic-release/release-notes-generator": "9.0.2", - "eslint": "7.25.0", + "babel-eslint": "10.1.0", + "eslint": "7.26.0", "eslint-config-airbnb-base": "14.2.1", "eslint-config-prettier": "8.3.0", "eslint-plugin-import": "2.22.1", @@ -64,9 +71,10 @@ "express": "4.17.1", "hbs": "4.1.2", "prettier": "2.2.1", + "rollup": "2.46.0", "semantic-release": "17.4.2", "stoppable": "1.1.0", "supertest": "6.1.3", - "tap": "15.0.6" + "tap": "15.0.9" } } diff --git a/release.config.js b/release.config.cjs similarity index 100% rename from release.config.js rename to release.config.cjs diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 00000000..f8678006 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,25 @@ +export default { + input: 'lib/layout.js', + external: [ + '@podium/schemas', + '@podium/context', + '@metrics/client', + '@podium/client', + '@podium/utils', + '@podium/proxy', + '@podium/utils', + 'abslog', + 'objobj', + 'path', + 'url', + 'fs', + ], + output: [ + { + exports: 'auto', + format: 'cjs', + dir: 'dist/', + preserveModules: true, + } + ], +}; diff --git a/tests/layout.js b/tests/layout.js index d980c30d..9648173d 100644 --- a/tests/layout.js +++ b/tests/layout.js @@ -1,15 +1,22 @@ -'use strict'; +/* eslint-disable no-param-reassign */ -const { test } = require('tap'); -const { destinationObjectStream } = require('@podium/test-utils'); -const { HttpIncoming, AssetJs, AssetCss } = require('@podium/utils'); -const PodletClientResponse = require('@podium/client/lib/response'); -const stoppable = require('stoppable'); -const express = require('express'); -const request = require('supertest'); -const Podlet = require('@podium/podlet'); +import tap from 'tap'; +import { destinationObjectStream } from '@podium/test-utils'; +import { HttpIncoming, AssetJs, AssetCss } from '@podium/utils'; +import stoppable from 'stoppable'; +import express from 'express'; +import request from 'supertest'; +import Podlet from '@podium/podlet'; -const Layout = require(".."); + +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; +import Layout from '../lib/layout.js'; + +const currentDirectory = dirname(fileURLToPath(import.meta.url)); +const pkgJson = fs.readFileSync(join(currentDirectory, '../package.json'), 'utf-8'); +const pkg = JSON.parse(pkgJson); const SIMPLE_REQ = { headers: {}, @@ -25,54 +32,54 @@ const DEFAULT_OPTIONS = { name: 'foo', pathname: '/' }; * Constructor */ -test('Layout() - instantiate new layout object - should create an object', t => { +tap.test('Layout() - instantiate new layout object - should create an object', t => { const layout = new Layout({ name: 'foo', pathname: '/' }); t.ok(layout instanceof Layout); t.end(); }); -test('Layout() - object tag - should be PodiumLayout', t => { +tap.test('Layout() - object tag - should be PodiumLayout', t => { const layout = new Layout({ name: 'foo', pathname: '/' }); t.equal(Object.prototype.toString.call(layout), '[object PodiumLayout]'); t.end(); }); -test('Layout() - no value given to "name" argument - should throw', t => { +tap.test('Layout() - no value given to "name" argument - should throw', t => { t.throws(() => { const layout = new Layout({ pathname: '/' }); // eslint-disable-line no-unused-vars }, 'The value, "", for the required argument "name" on the Layout constructor is not defined or not valid.'); t.end(); }); -test('Layout() - invalid value given to "name" argument - should throw', t => { +tap.test('Layout() - invalid value given to "name" argument - should throw', t => { t.throws(() => { const layout = new Layout({ name: 'foo bar', pathname: '/' }); // eslint-disable-line no-unused-vars }, 'The value, "foo bar", for the required argument "name" on the Layout constructor is not defined or not valid.'); t.end(); }); -test('Layout() - no value given to "pathname" argument - should throw', t => { +tap.test('Layout() - no value given to "pathname" argument - should throw', t => { t.throws(() => { const layout = new Layout({ name: 'foo' }); // eslint-disable-line no-unused-vars }, 'The value, "", for the required argument "pathname" on the Layout constructor is not defined or not valid.'); t.end(); }); -test('Layout() - invalid value given to "name" argument - should throw', t => { +tap.test('Layout() - invalid value given to "name" argument - should throw', t => { t.throws(() => { const layout = new Layout({ name: 'foo', pathname: 'foo bar' }); // eslint-disable-line no-unused-vars }, 'The value, "foo bar", for the required argument "pathname" on the Layout constructor is not defined or not valid.'); t.end(); }); -test('Layout() - should collect metric with version info', t => { +tap.test('Layout() - should collect metric with version info', t => { const layout = new Layout(DEFAULT_OPTIONS); const dest = destinationObjectStream(arr => { t.equal(arr[0].name, 'podium_layout_version_info'); t.equal(arr[0].labels[0].name, 'version'); // eslint-disable-next-line global-require - t.equal(arr[0].labels[0].value, require('../package.json').version); + t.equal(arr[0].labels[0].value, pkg.version); t.equal(arr[0].labels[1].name, 'major'); t.equal(arr[0].labels[2].name, 'minor'); t.equal(arr[0].labels[3].name, 'patch'); @@ -86,7 +93,7 @@ test('Layout() - should collect metric with version info', t => { }); }); -test('Layout() - metrics properly decorated', t => { +tap.test('Layout() - metrics properly decorated', t => { // podlet const podletApp = express(); @@ -219,7 +226,7 @@ test('Layout() - metrics properly decorated', t => { // .css() // ############################################# -test('.css() - call method with no arguments - should throw', (t) => { +tap.test('.css() - call method with no arguments - should throw', (t) => { const layout = new Layout(DEFAULT_OPTIONS); t.throws(() => { layout.css(); @@ -227,7 +234,7 @@ test('.css() - call method with no arguments - should throw', (t) => { t.end() }); -test('.css() - set legal absolute value on "value" argument - should set "css" to set value', t => { +tap.test('.css() - set legal absolute value on "value" argument - should set "css" to set value', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.css({ value: 'http://somewhere.remote.com' }); const result = JSON.parse(JSON.stringify(layout.cssRoute)); @@ -237,7 +244,7 @@ test('.css() - set legal absolute value on "value" argument - should set "css" t t.end(); }); -test('.css() - "options" argument as an array - should accept an array of values', t => { +tap.test('.css() - "options" argument as an array - should accept an array of values', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.css([{ value: '/foo/bar' }, { value: '/bar/foo' }]); const result = JSON.parse(JSON.stringify(layout.cssRoute)); @@ -248,7 +255,7 @@ test('.css() - "options" argument as an array - should accept an array of values t.end(); }); -test('.css() - "options" argument as an array - call method twice - should set all values', t => { +tap.test('.css() - "options" argument as an array - call method twice - should set all values', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.css([{ value: '/foo/bar' }, { value: '/bar/foo' }]); layout.css([{ value: '/foo/bar/baz' }, { value: '/bar/foo/baz' }]); @@ -262,7 +269,7 @@ test('.css() - "options" argument as an array - call method twice - should set a t.end(); }); -test('.css() - "options" argument as an array - should NOT set additional keys', t => { +tap.test('.css() - "options" argument as an array - should NOT set additional keys', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.css([ { value: '/foo/bar', fake: 'prop' }, @@ -276,7 +283,7 @@ test('.css() - "options" argument as an array - should NOT set additional keys', t.end(); }); -test('.css() - passing an instance of AssetsCss - should return set value', t => { +tap.test('.css() - passing an instance of AssetsCss - should return set value', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.css(new AssetCss({ value: '/foo/bar', type: 'text/css' })); const result = JSON.parse(JSON.stringify(layout.cssRoute)); @@ -290,7 +297,7 @@ test('.css() - passing an instance of AssetsCss - should return set value', t => // .js() // ############################################# -test('.js() - call method with no arguments - should throw', (t) => { +tap.test('.js() - call method with no arguments - should throw', (t) => { const layout = new Layout(DEFAULT_OPTIONS); t.throws(() => { layout.js(); @@ -298,7 +305,7 @@ test('.js() - call method with no arguments - should throw', (t) => { t.end() }); -test('.js() - passing an instance of AssetsJs - should return set value', t => { +tap.test('.js() - passing an instance of AssetsJs - should return set value', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.js(new AssetJs({ value: '/foo/bar', type: 'module' })); const result = JSON.parse(JSON.stringify(layout.jsRoute)); @@ -306,7 +313,7 @@ test('.js() - passing an instance of AssetsJs - should return set value', t => { t.end(); }); -test('.js() - set legal absolute value on "value" argument - should set "js" to set value', t => { +tap.test('.js() - set legal absolute value on "value" argument - should set "js" to set value', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.js({ value: 'http://somewhere.remote.com' }); const result = JSON.parse(JSON.stringify(layout.jsRoute)); @@ -316,7 +323,7 @@ test('.js() - set legal absolute value on "value" argument - should set "js" to t.end(); }); -test('.js() - set illegal value on "value" argument - should throw', t => { +tap.test('.js() - set illegal value on "value" argument - should throw', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.js({ value: '/foo/bar' }); layout.js({ value: '/bar/foo', type: 'module' }); @@ -328,7 +335,7 @@ test('.js() - set illegal value on "value" argument - should throw', t => { t.end(); }); -test('.js() - "type" argument is set to "module" - should set "type" to "module"', t => { +tap.test('.js() - "type" argument is set to "module" - should set "type" to "module"', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.js({ value: '/foo/bar' }); layout.js({ value: '/bar/foo', type: 'module' }); @@ -340,7 +347,7 @@ test('.js() - "type" argument is set to "module" - should set "type" to "module" t.end(); }); -test('.js() - "options" argument as an array - should accept an array of values', t => { +tap.test('.js() - "options" argument as an array - should accept an array of values', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.js([{ value: '/foo/bar' }, { value: '/bar/foo', type: 'module' }]); const result = JSON.parse(JSON.stringify(layout.jsRoute)); @@ -351,7 +358,7 @@ test('.js() - "options" argument as an array - should accept an array of values' t.end(); }); -test('.js() - "options" argument as an array - call method twice - should set all values', t => { +tap.test('.js() - "options" argument as an array - call method twice - should set all values', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.js([{ value: '/foo/bar' }, { value: '/bar/foo', type: 'module' }]); layout.js([ @@ -368,7 +375,7 @@ test('.js() - "options" argument as an array - call method twice - should set al t.end(); }); -test('.js() - "options" argument as an array - should NOT set additional keys', t => { +tap.test('.js() - "options" argument as an array - should NOT set additional keys', t => { const layout = new Layout(DEFAULT_OPTIONS); layout.js([ { value: '/foo/bar', fake: 'prop' }, @@ -382,7 +389,7 @@ test('.js() - "options" argument as an array - should NOT set additional keys', t.end(); }); -test('.js() - data attribute object - should convert to array of key / value objects', (t) => { +tap.test('.js() - data attribute object - should convert to array of key / value objects', (t) => { const layout = new Layout(DEFAULT_OPTIONS); layout.js([ { @@ -417,7 +424,7 @@ test('.js() - data attribute object - should convert to array of key / value obj // .process() // ############################################# -test('.process() - call method with HttpIncoming - should return HttpIncoming', async t => { +tap.test('.process() - call method with HttpIncoming - should return HttpIncoming', async t => { const layout = new Layout(DEFAULT_OPTIONS); const incoming = new HttpIncoming(SIMPLE_REQ, SIMPLE_RES); const result = await layout.process(incoming); @@ -425,7 +432,7 @@ test('.process() - call method with HttpIncoming - should return HttpIncoming', t.end(); }); -test('.process() - idempotence - manipulating the HttpIncoming should not affect layout', async t => { +tap.test('.process() - idempotence - manipulating the HttpIncoming should not affect layout', async t => { const layout = new Layout(DEFAULT_OPTIONS); t.same(layout.jsRoute, []); t.same(layout.cssRoute, []); @@ -435,19 +442,17 @@ test('.process() - idempotence - manipulating the HttpIncoming should not affect await layout.process(incoming); // Simulate layout route - incoming.podlets = [ - new PodletClientResponse({ - js: [{ value: '/foo/bar' }], - css: [{ value: '/bar/foo' }], - }), - ]; + incoming.podlets = [{ + js: [{ value: '/foo/bar' }], + css: [{ value: '/bar/foo' }], + }]; t.same(layout.jsRoute, []); t.same(layout.cssRoute, []); t.end(); }); -test('Layout() - rendering using a string', async t => { +tap.test('Layout() - rendering using a string', async t => { const app = express(); const layout = new Layout({ @@ -472,7 +477,7 @@ test('Layout() - rendering using a string', async t => { t.end(); }); -test('Layout() - rendering using a string - with assets', async t => { +tap.test('Layout() - rendering using a string - with assets', async t => { const app = express(); const layout = new Layout({ @@ -506,7 +511,7 @@ test('Layout() - rendering using a string - with assets', async t => { t.end(); }); -test('Layout() - setting a custom view template', async t => { +tap.test('Layout() - setting a custom view template', async t => { const app = express(); const layout = new Layout({ @@ -537,7 +542,7 @@ test('Layout() - setting a custom view template', async t => { t.end(); }); -test('Layout() - request url parsing', async t => { +tap.test('Layout() - request url parsing', async t => { const app = express(); const layout = new Layout({