diff --git a/package.json b/package.json index ff3fa38f..c664302a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kotlin-playground", - "version": "1.28.0", + "version": "1.29.0-alpha.1", "description": "Self-contained component to embed in websites for running Kotlin code", "keywords": [ "kotlin", diff --git a/playwright.config.ts b/playwright.config.ts index 79e280f0..6bae8beb 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,6 +1,7 @@ import { env } from 'process'; import { config as dotenv } from 'dotenv'; import { defineConfig, devices } from '@playwright/test'; +import { isKeyOfObject } from './src/utils/types'; dotenv({ path: `.env.local`, override: true }); @@ -24,7 +25,7 @@ const isDevMode = Boolean(mode === 'DEV'); export default defineConfig({ testDir: './tests', - testMatch: /.*\.e2e\.tsx?$/, + testMatch: /.*\.(e2e|test)\.tsx?$/, snapshotPathTemplate: `{testDir}/{testFileDir}/__screenshots__/${mode.toLowerCase()}/{projectName}/{testFilePath}-{arg}{ext}`, timeout: 30000, @@ -58,10 +59,3 @@ export default defineConfig({ use: { ...devices[project] }, })), }); - -export function isKeyOfObject( - key: string | number | symbol, - obj: T, -): key is keyof T { - return key in obj; -} diff --git a/src/config.js b/src/config.js index d7866a11..4a05751f 100644 --- a/src/config.js +++ b/src/config.js @@ -1,5 +1,5 @@ import {getConfigFromElement, getCurrentScript} from './utils'; -import TargetPlatform from "./target-platform"; +import {TargetPlatforms} from "./utils/platforms"; const currentScript = getCurrentScript(); @@ -16,22 +16,22 @@ export const API_URLS = { let url; switch (platform) { - case TargetPlatform.JAVA: + case TargetPlatforms.JAVA: url = `${this.server}/api/${version}/compiler/run`; break; - case TargetPlatform.CANVAS: + case TargetPlatforms.CANVAS: url = `${this.server}/api/${version}/compiler/translate`; break; - case TargetPlatform.JS: + case TargetPlatforms.JS: url = `${this.server}/api/${version}/compiler/translate`; break; - case TargetPlatform.JS_IR: + case TargetPlatforms.JS_IR: url = `${this.server}/api/${version}/compiler/translate?ir=true`; break; - case TargetPlatform.WASM: + case TargetPlatforms.WASM: url = `${this.server}/api/${version}/compiler/translate?ir=true&compiler=wasm`; break; - case TargetPlatform.JUNIT: + case TargetPlatforms.JUNIT: url = `${this.server}/api/${version}/compiler/test`; break; default: diff --git a/src/executable-code/executable-fragment.js b/src/executable-code/executable-fragment.js index 9a62520d..ffbebadf 100644 --- a/src/executable-code/executable-fragment.js +++ b/src/executable-code/executable-fragment.js @@ -6,17 +6,19 @@ import directives from 'monkberry-directives'; import 'monkberry-events'; import ExecutableCodeTemplate from './executable-fragment.monk'; import WebDemoApi from '../webdemo-api'; -import TargetPlatform from "../target-platform"; +import {TargetPlatforms, isJsRelated, isJavaRelated} from "../utils/platforms"; import JsExecutor from "../js-executor" + import { - countLines, - escapeRegExp, MARK_PLACEHOLDER_CLOSE, + escapeRegExp, + MARK_PLACEHOLDER_CLOSE, MARK_PLACEHOLDER_OPEN, SAMPLE_END, SAMPLE_START, - THEMES, unEscapeString -} from "../utils"; +} from "../utils/escape"; + +import { countLines, THEMES } from "../utils"; import debounce from 'debounce'; import CompletionView from "../view/completion-view"; import {processErrors} from "../view/output-view"; @@ -94,7 +96,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate { let sample; let hasMarkers = false; let platform = state.targetPlatform; - if (state.compilerVersion && TargetPlatform.isJsRelated(platform)) { + if (state.compilerVersion && isJsRelated(platform)) { this.jsExecutor = new JsExecutor(state.compilerVersion); } @@ -257,7 +259,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate { onConsoleCloseButtonEnter() { const {jsLibs, onCloseConsole, targetPlatform } = this.state; // creates a new iframe and removes the old one, thereby stops execution of any running script - if (TargetPlatform.isJsRelated(targetPlatform)) + if (isJsRelated(targetPlatform)) this.jsExecutor.reloadIframeScripts(jsLibs, this.getNodeForMountIframe(), targetPlatform); this.update({output: "", openConsole: false, exception: null}); if (onCloseConsole) onCloseConsole(); @@ -291,7 +293,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate { }); if (onOpenConsole) onOpenConsole(); //open when waitingForOutput=true if (onRun) onRun(); - if (TargetPlatform.isJavaRelated(targetPlatform)) { + if (isJavaRelated(targetPlatform)) { WebDemoApi.executeKotlinCode( this.getCode(), compilerVersion, @@ -349,7 +351,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate { state.output = ""; if (onCloseConsole) onCloseConsole(); } - if (targetPlatform === TargetPlatform.CANVAS) { + if (targetPlatform === TargetPlatforms.CANVAS) { if (onOpenConsole) onOpenConsole(); state.openConsole = true; } diff --git a/src/executable-code/index.js b/src/executable-code/index.js index f6272a02..37145637 100644 --- a/src/executable-code/index.js +++ b/src/executable-code/index.js @@ -18,20 +18,12 @@ import 'codemirror/mode/swift/swift'; import merge from 'deepmerge'; import Set from 'es6-set/polyfill'; import defaultConfig, {API_URLS} from '../config'; -import { - arrayFrom, - escapeRegExp, - getConfigFromElement, - insertAfter, MARK_PLACEHOLDER_CLOSE, MARK_PLACEHOLDER_OPEN, - READ_ONLY_TAG, - replaceWhiteSpaces, SAMPLE_END, SAMPLE_START, - THEMES -} from '../utils'; +import {arrayFrom, getConfigFromElement, insertAfter, READ_ONLY_TAG, replaceWhiteSpaces, THEMES} from '../utils'; import WebDemoApi from "../webdemo-api"; -import TargetPlatform from '../target-platform' import ExecutableFragment from './executable-fragment'; import { generateCrosslink } from '../lib/crosslink'; import '../styles.scss'; +import {getTargetById, isJsRelated, TargetPlatforms} from "../utils/platforms"; const INITED_ATTRIBUTE_NAME = 'data-kotlin-playground-initialized'; const DEFAULT_INDENT = 4; @@ -94,7 +86,7 @@ export default class ExecutableCode { const args = targetNode.hasAttribute(ATTRIBUTES.ARGUMENTS) ? targetNode.getAttribute(ATTRIBUTES.ARGUMENTS) : ""; const hiddenDependencies = this.getHiddenDependencies(targetNode); const outputHeight = targetNode.getAttribute(ATTRIBUTES.OUTPUT_HEIGHT) || null; - const targetPlatform = TargetPlatform.getById(targetNode.getAttribute(ATTRIBUTES.PLATFORM)); + const targetPlatform = getTargetById(targetNode.getAttribute(ATTRIBUTES.PLATFORM)); const targetNodeStyle = targetNode.getAttribute(ATTRIBUTES.STYLE); const jsLibs = this.getJsLibraries(targetNode, targetPlatform); const isFoldedButton = targetNode.getAttribute(ATTRIBUTES.FOLDED_BUTTON) !== "false"; @@ -130,13 +122,8 @@ export default class ExecutableCode { ) ); - if (!isCrosslinkDisabled) crosslink = generateCrosslink({ - code: code - .replace(new RegExp(escapeRegExp(MARK_PLACEHOLDER_OPEN), 'g'), "") - .replace(new RegExp(escapeRegExp(MARK_PLACEHOLDER_CLOSE), 'g'), "") - .replace(new RegExp(escapeRegExp(SAMPLE_START), 'g'), "") - .replace(new RegExp(escapeRegExp(SAMPLE_END), 'g'), ""), - + if (!isCrosslinkDisabled) crosslink = generateCrosslink(code, { + code: code, targetPlatform: targetPlatform.id, // hiddenDependencies, // multi-file support needs compilerVersion: cfg.compilerVersion, @@ -209,8 +196,8 @@ export default class ExecutableCode { * @returns {Set} - set of additional libraries */ getJsLibraries(targetNode, platform) { - if (TargetPlatform.isJsRelated(platform)) { - if (platform === TargetPlatform.WASM) { + if (isJsRelated(platform)) { + if (platform === TargetPlatforms.WASM) { return new Set() } const jsLibs = targetNode.getAttribute(ATTRIBUTES.JS_LIBS); diff --git a/src/js-executor/index.js b/src/js-executor/index.js index c160237e..9b4ff4e1 100644 --- a/src/js-executor/index.js +++ b/src/js-executor/index.js @@ -1,8 +1,8 @@ import './index.scss' import {API_URLS} from "../config"; -import TargetPlatform from "../target-platform"; import {showJsException} from "../view/output-view"; import {processingHtmlBrackets} from "../utils"; +import { TargetPlatforms } from "../utils/platforms"; const INIT_SCRIPT = "if(kotlin.BufferedOutput!==undefined){kotlin.out = new kotlin.BufferedOutput()}" + "else{kotlin.kotlin.io.output = new kotlin.kotlin.io.BufferedOutput()}"; @@ -26,11 +26,11 @@ export default class JsExecutor { } async executeJsCode(jsCode, wasm, jsLibs, platform, outputHeight, theme, onError) { - if (platform === TargetPlatform.CANVAS) { + if (platform === TargetPlatforms.CANVAS) { this.iframe.style.display = "block"; if (outputHeight) this.iframe.style.height = `${outputHeight}px`; } - if (platform === TargetPlatform.WASM) { + if (platform === TargetPlatforms.WASM) { return await this.executeWasm(jsCode, wasm, theme, onError) } return await this.execute(jsCode, jsLibs, theme, onError, platform); @@ -39,7 +39,7 @@ export default class JsExecutor { async execute(jsCode, jsLibs, theme, onError, platform) { const loadedScripts = (this.iframe.contentDocument || this.iframe.document).getElementsByTagName('script').length; let offset; - if (platform === TargetPlatform.JS_IR) { + if (platform === TargetPlatforms.JS_IR) { // 1 scripts by default: INIT_SCRIPT_IR offset = 1; } else { @@ -106,15 +106,15 @@ export default class JsExecutor { node.appendChild(this.iframe); let iframeDoc = this.iframe.contentDocument || this.iframe.document; iframeDoc.open(); - if (targetPlatform === TargetPlatform.JS || targetPlatform === TargetPlatform.CANVAS) { + if (targetPlatform === TargetPlatforms.JS || targetPlatform === TargetPlatforms.CANVAS) { const kotlinScript = API_URLS.KOTLIN_JS + `${normalizeJsVersion(this.kotlinVersion)}/kotlin.js`; iframeDoc.write(""); } - if (targetPlatform !== TargetPlatform.WASM) { + if (targetPlatform !== TargetPlatforms.WASM) { for (let lib of jsLibs) { iframeDoc.write(""); } - if (targetPlatform === TargetPlatform.JS_IR) { + if (targetPlatform === TargetPlatforms.JS_IR) { iframeDoc.write(``); } else { iframeDoc.write(``); diff --git a/src/lib/crosslink.js b/src/lib/crosslink.js deleted file mode 100644 index dbc825e4..00000000 --- a/src/lib/crosslink.js +++ /dev/null @@ -1,7 +0,0 @@ -import { compressToBase64 } from 'lz-string'; - -export function generateCrosslink(options) { - const base = JSON.stringify(options); - const compressed = compressToBase64(base); - return `https://play.kotlinlang.org/editor/v1/${encodeURIComponent(compressed)}`; -} diff --git a/src/lib/crosslink.ts b/src/lib/crosslink.ts new file mode 100644 index 00000000..651e2e86 --- /dev/null +++ b/src/lib/crosslink.ts @@ -0,0 +1,51 @@ +import { compressToBase64 } from 'lz-string'; + +import { isKeyOfObject } from '../utils/types'; +import { TargetPlatforms, TargetPlatformsKeys } from '../utils/platforms'; + +import { + escapeRegExp, + MARK_PLACEHOLDER_CLOSE, + MARK_PLACEHOLDER_OPEN, + SAMPLE_END, + SAMPLE_START, +} from '../utils/escape'; + +type LinkOptions = { + targetPlatform?: TargetPlatformsKeys | Lowercase; + compilerVersion?: string; +}; + +/** + * Assign the project to an employee. + * @param {Object} code - The employee who is responsible for the project. + * @param {Object} options - The employee who is responsible for the project. + * @param {string} options.targetPlatform - The name of the employee. + * @param {string} options.compilerVersion - The employee's department. + */ +export function generateCrosslink(code: string, options?: LinkOptions) { + const opts: { code: string } & LinkOptions = { + code: code + .replace(new RegExp(escapeRegExp(MARK_PLACEHOLDER_OPEN), 'g'), '') + .replace(new RegExp(escapeRegExp(MARK_PLACEHOLDER_CLOSE), 'g'), '') + .replace(new RegExp(escapeRegExp(SAMPLE_START), 'g'), '') + .replace(new RegExp(escapeRegExp(SAMPLE_END), 'g'), ''), + }; + + if (options && options.targetPlatform) { + const target = + options.targetPlatform && options.targetPlatform.toUpperCase(); + + if (!isKeyOfObject(target, TargetPlatforms)) + throw new Error('Invalid target platform'); + + opts.targetPlatform = options.targetPlatform; + } + + if (options && options.compilerVersion) + opts.compilerVersion = options.compilerVersion; + + return `https://play.kotlinlang.org/editor/v1/${encodeURIComponent( + compressToBase64(JSON.stringify(opts)), + )}`; +} diff --git a/src/target-platform.js b/src/target-platform.js deleted file mode 100644 index 36d52be2..00000000 --- a/src/target-platform.js +++ /dev/null @@ -1,41 +0,0 @@ -class TargetPlatform { - constructor(id, printableName) { - this.id = id; - this.printableName = printableName; - } - - static getById(id) { - switch (id) { - case "js": - return TargetPlatform.JS; - case "js-ir": - return TargetPlatform.JS_IR; - case "wasm": - return TargetPlatform.WASM; - case "junit": - return TargetPlatform.JUNIT; - case "canvas": - return TargetPlatform.CANVAS; - default: - return TargetPlatform.JAVA; - } - } - - static isJavaRelated(platform) { - return platform === TargetPlatform.JAVA || platform === TargetPlatform.JUNIT - } - - static isJsRelated(platform) { - return platform === TargetPlatform.JS || platform === TargetPlatform.JS_IR || platform === TargetPlatform.CANVAS || - platform === TargetPlatform.WASM - } -} - -TargetPlatform.JS = new TargetPlatform('js', 'JavaScript'); -TargetPlatform.JS_IR = new TargetPlatform('js-ir', 'JavaScript IR'); -TargetPlatform.WASM = new TargetPlatform('wasm', 'Wasm'); -TargetPlatform.JAVA = new TargetPlatform('java', 'JVM'); -TargetPlatform.JUNIT = new TargetPlatform('junit', 'JUnit'); -TargetPlatform.CANVAS = new TargetPlatform('canvas', 'JavaScript(canvas)'); - -export default TargetPlatform diff --git a/src/utils/escape.js b/src/utils/escape.js new file mode 100644 index 00000000..50926ced --- /dev/null +++ b/src/utils/escape.js @@ -0,0 +1,33 @@ +export const SAMPLE_START = '//sampleStart'; +export const SAMPLE_END = '//sampleEnd'; + +export const MARK_PLACEHOLDER_OPEN = "[mark]"; +export const MARK_PLACEHOLDER_CLOSE = "[/mark]"; + + +/** + * Use instead of @escape-string-regexp + */ + +export /*#__PURE__*/ function escapeRegExp(str) { + return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); +} + +/** + * Unescape special characters from string + * @param string + * @returns {string} + */ +export /*#__PURE__*/ function unEscapeString(string) { + const tagsToReplace = { + "<": "&lt;", + ">": "&gt;", + "&": "&", + " ": "%20" + }; + let unEscapedString = string; + Object.keys(tagsToReplace).forEach(function (key) { + unEscapedString = unEscapedString.replace(new RegExp(tagsToReplace[key], 'g'), key) + }); + return unEscapedString +} diff --git a/src/utils.js b/src/utils/index.js similarity index 85% rename from src/utils.js rename to src/utils/index.js index 43e23baa..1bfe34a8 100644 --- a/src/utils.js +++ b/src/utils/index.js @@ -1,5 +1,5 @@ import merge from 'deepmerge'; -import defaultConfig from './config'; +import defaultConfig from '../config'; /** * Codemirror themes. @@ -88,13 +88,6 @@ export function getCurrentScript() { return scripts[scripts.length - 1] || null; } -/** - * Use instead of @escape-string-regexp - */ -export function escapeRegExp(str) { - return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); -} - /** * @param {Element} newNode * @param {Element} referenceNode @@ -120,25 +113,6 @@ export function processingHtmlBrackets(string) { return unEscapedString } -/** - * Unescape special characters from string - * @param string - * @returns {string} - */ -export function unEscapeString(string) { - const tagsToReplace = { - "<": "&lt;", - ">": "&gt;", - "&": "&", - " ": "%20" - }; - let unEscapedString = string; - Object.keys(tagsToReplace).forEach(function (key) { - unEscapedString = unEscapedString.replace(new RegExp(tagsToReplace[key], 'g'), key) - }); - return unEscapedString -} - /** * convert all `<` and `>` to `<` and `>` * @param string diff --git a/src/utils/platforms/TargetPlatform.ts b/src/utils/platforms/TargetPlatform.ts new file mode 100644 index 00000000..2731dab6 --- /dev/null +++ b/src/utils/platforms/TargetPlatform.ts @@ -0,0 +1,9 @@ +export default class TargetPlatform { + private id: string; + private printableName: string; + + constructor(id: string, printableName: string) { + this.id = id; + this.printableName = printableName; + } +} diff --git a/src/utils/platforms/TargetPlatforms.ts b/src/utils/platforms/TargetPlatforms.ts new file mode 100644 index 00000000..68b1b7d8 --- /dev/null +++ b/src/utils/platforms/TargetPlatforms.ts @@ -0,0 +1,12 @@ +import TargetPlatform from './TargetPlatform'; + +export const TargetPlatforms = { + JS: new TargetPlatform('js', 'JavaScript'), + JS_IR: new TargetPlatform('js-ir', 'JavaScript IR'), + WASM: new TargetPlatform('wasm', 'Wasm'), + JAVA: new TargetPlatform('java', 'JVM'), + JUNIT: new TargetPlatform('junit', 'JUnit'), + CANVAS: new TargetPlatform('canvas', 'JavaScript(canvas)'), +} as const; + +export type TargetPlatformsKeys = keyof typeof TargetPlatforms; diff --git a/src/utils/platforms/index.ts b/src/utils/platforms/index.ts new file mode 100644 index 00000000..602e7940 --- /dev/null +++ b/src/utils/platforms/index.ts @@ -0,0 +1,28 @@ +import { isKeyOfObject } from '../types'; + +import TargetPlatform from './TargetPlatform'; +import { TargetPlatforms } from './TargetPlatforms'; + +export function getTargetById(id?: string | null) { + const key = id && id.toUpperCase(); + return key && isKeyOfObject(key, TargetPlatforms) + ? TargetPlatforms[key] + : TargetPlatforms.JAVA; +} + +export function isJavaRelated(platform: TargetPlatform) { + return ( + platform === TargetPlatforms.JAVA || platform === TargetPlatforms.JUNIT + ); +} + +export function isJsRelated(platform: TargetPlatform) { + return ( + platform === TargetPlatforms.JS || + platform === TargetPlatforms.JS_IR || + platform === TargetPlatforms.CANVAS || + platform === TargetPlatforms.WASM + ); +} + +export * from './TargetPlatforms'; diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 00000000..8084ee71 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,6 @@ +export function isKeyOfObject( + key: string | number | symbol, + obj: T, +): key is keyof T { + return key in obj; +} diff --git a/src/webdemo-api.js b/src/webdemo-api.js index c571b63d..60080e45 100644 --- a/src/webdemo-api.js +++ b/src/webdemo-api.js @@ -1,7 +1,7 @@ import {fetch} from 'whatwg-fetch'; -import TargetPlatform from "./target-platform"; import {API_URLS} from "./config"; -import flatten from 'flatten' +import flatten from 'flatten'; +import {TargetPlatforms} from './utils/platforms'; import { findSecurityException, getExceptionCauses, @@ -54,7 +54,7 @@ export default class WebDemoApi { const MINIMAL_MINOR_VERSION_WASM = 9 const minor = parseInt(compilerVersion.split(".")[1]); - if (platform === TargetPlatform.JS_IR && minor < MINIMAL_MINOR_VERSION_IR) { + if (platform === TargetPlatforms.JS_IR && minor < MINIMAL_MINOR_VERSION_IR) { return Promise.resolve({ output: "", errors: [{ @@ -65,7 +65,7 @@ export default class WebDemoApi { }) } - if (platform === TargetPlatform.WASM && minor < MINIMAL_MINOR_VERSION_WASM) { + if (platform === TargetPlatforms.WASM && minor < MINIMAL_MINOR_VERSION_WASM) { return Promise.resolve({ output: "", errors: [{ @@ -110,10 +110,10 @@ export default class WebDemoApi { output = processErrors(errors, theme); } else { switch (platform) { - case TargetPlatform.JAVA: + case TargetPlatforms.JAVA: if (data.text) output = processJVMOutput(data.text, theme); break; - case TargetPlatform.JUNIT: + case TargetPlatforms.JUNIT: data.testResults ? output = processJUnitResults(data.testResults, onTestPassed, onTestFailed) : output = processJVMOutput(data.text || '', theme); break; } diff --git a/tests/crosslink.test.ts b/tests/crosslink.test.ts new file mode 100644 index 00000000..2d50f122 --- /dev/null +++ b/tests/crosslink.test.ts @@ -0,0 +1,86 @@ +import {expect, test} from '@playwright/test'; +import {promises as fs} from 'fs'; +import {decompressFromBase64} from 'lz-string'; + +const prefix = 'https://play.kotlinlang.org/editor/v1/' as const; + +test.describe('crosslink: library', () => { + test('exports', async () => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const module = require('../dist/crosslink'); + expect(module).toBeDefined(); + + const { generateCrosslink } = module; + + expect(typeof generateCrosslink).toEqual('function'); + + // Pass just codeWithSample + checkLink(generateCrosslink('simple'), { code: 'simple' }); + + // Pass platforms with codeWithSample + checkLink(generateCrosslink('platform', { targetPlatform: 'JAVA' }), { + code: 'platform', + targetPlatform: 'JAVA', + }); + + // Invalid target + expect(() => + generateCrosslink('platform', { targetPlatform: 'NOT_A_PLATFORM' }), + ).toThrow(); + + // Pass compilerVersion with codeWithSample + checkLink(generateCrosslink('version', { compilerVersion: '1.5.21' }), { + code: 'version', + compilerVersion: '1.5.21', + }); + + // Pass random with codeWithSample + checkLink(generateCrosslink('random', { randomProperty: '1.5.21' }), { + code: 'random', + }); + + //language=kotlin + const codeWithSample = `fun main(args: Array) { + //sampleStart + val (name, value) = Pair("Kitty", "Kiss") + println(name) + println(value) + //sampleEnd + }`; + + checkLink(generateCrosslink(codeWithSample), { + //language=text + code: `fun main(args: Array) { +${' '} + val (name, value) = Pair("Kitty", "Kiss") + println(name) + println(value) +${' '} + }` + }); + + //language=text + const codeWithMark = `fun containsEven(collection: Collection): Boolean = collection.any {[mark]TODO()[/mark]}`; + + checkLink(generateCrosslink(codeWithMark), { + //language=kotlin + code: `fun containsEven(collection: Collection): Boolean = collection.any {TODO()}` + }); + }); + + test('definition', async () => { + test.fixme(true, "The definition isn't implemented yet"); + const stats = await fs.stat('../dist/crosslink.d.ts'); + expect(stats.size).toBeGreaterThan(0); + }); +}); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function checkLink(link: string, expected: Record) { + expect(link.startsWith(prefix)).toBeTruthy(); + + const output = decodeURIComponent(link.substring(prefix.length)); + const payload = JSON.parse(decompressFromBase64(output)); + + expect(payload).toEqual(expected); +} diff --git a/webpack.config.js b/webpack.config.js index 6e716318..2a2cbd93 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -10,23 +10,14 @@ module.exports = (params = {}) => { const libraryName = 'KotlinPlayground'; const webDemoUrl = params.webDemoUrl || 'https://api.kotlinlang.org/'; const examplesPath = isServer ? '' : 'examples/'; + const pathDist = path.resolve(__dirname, 'dist'); - const config = { + const common = { mode: env, - entry: { - [mainEntryName]: ['./src/index'], - REMOVE_ME: [ - `!!file-loader?name=${examplesPath}examples.css!github-markdown-css/github-markdown.css`, - `!!file-loader?name=${examplesPath}examples-highlight.css!highlight.js/styles/github.css`, - ], - }, output: { - path: path.resolve(__dirname, 'dist'), - filename: '[name].js', - library: libraryName, - libraryTarget: 'umd', - libraryExport: 'default', + path: pathDist, + filename: '[name].js' }, devtool: 'source-map', @@ -68,28 +59,68 @@ module.exports = (params = {}) => { }, plugins: [ + new webpack.optimize.ModuleConcatenationPlugin(), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(env), + }, + }), + ], + }; + + const bundle = { + ...common, + + entry: { + [mainEntryName]: ['./src/index'], + REMOVE_ME: [ + `!!file-loader?name=${examplesPath}examples.css!github-markdown-css/github-markdown.css`, + `!!file-loader?name=${examplesPath}examples-highlight.css!highlight.js/styles/github.css`, + ], + }, + + output: { + ...common.output, + library: libraryName, + libraryTarget: 'umd', + libraryExport: 'default', + }, + + plugins: [ + ...common.plugins, + new HtmlPlugin({ template: 'examples.md', filename: isServer ? 'index.html' : 'examples/index.html', inject: false, }), - new webpack.optimize.ModuleConcatenationPlugin(), - new webpack.DefinePlugin({ __WEBDEMO_URL__: JSON.stringify(webDemoUrl), __IS_PRODUCTION__: isProduction, __LIBRARY_NAME__: JSON.stringify(libraryName), - 'process.env': { - NODE_ENV: JSON.stringify(env), - }, }), ], - devServer: { static: path.resolve(__dirname, 'src'), }, - }; + } + + const crosslink = { + ...common, + target: 'node', + entry: { + crosslink: './src/lib/crosslink', + }, + output: { + ...common.output, + globalObject: 'this', + library: { + name: 'crosslink', + type: 'umd', + }, + }, + } - return config; + return [bundle, crosslink]; };