[next] Embed: Defer login/logout until ready (#2123)

* feat: Embed defer login/-out until ready

* fix: make remove work with lazy render

* fix: typo

* fix: another typo

* fix: test

* chore: replace query-string for querystringify

* fix: types

* chore: small refactor

* feat: added webpack analzyer

* chore: rename compile -> generate

* fix: fix scripts and improve bundle size

* fix: lodash webpack plugin
This commit is contained in:
Kiwi
2018-12-15 01:07:09 +01:00
committed by Wyatt Johnson
parent 6f538d3235
commit 097294909b
42 changed files with 531 additions and 280 deletions
+4 -4
View File
@@ -56,8 +56,8 @@ jobs:
- attach_workspace:
at: ~/coralproject/talk
- run:
name: Compile schemas and types
command: npm run compile
name: Generate schemas and types
command: npm run generate
- run:
name: Perform linting
command: npm run lint
@@ -74,8 +74,8 @@ jobs:
- attach_workspace:
at: ~/coralproject/talk
- run:
name: Compile schemas and types
command: npm run compile
name: Generate schemas and types
command: npm run generate
- run:
name: Perform testing
# We're running these tests in band to avoid errors where the circleci
+1 -1
View File
@@ -15,7 +15,7 @@ COPY . /usr/src/app
# Install build static assets and clear caches.
RUN NODE_ENV=development npm install && \
npm run compile && \
npm run generate && \
npm run build && \
npm prune --production
+25 -25
View File
@@ -8,13 +8,13 @@ import {
const config: Config = {
rootDir: path.resolve(__dirname, "../src"),
watchers: {
compileSchema: {
generateSchemaTypes: {
paths: ["core/server/**/*.graphql"],
executor: new CommandExecutor("npx gulp server:schema", {
runOnInit: true,
}),
},
compileRelayStream: {
generateRelayStream: {
paths: [
"core/client/stream/**/*.ts",
"core/client/stream/**/*.tsx",
@@ -27,11 +27,11 @@ const config: Config = {
"**/test/**/*",
"core/**/*.spec.*",
],
executor: new CommandExecutor("npm run compile:relay-stream", {
executor: new CommandExecutor("npm run generate:relay-stream", {
runOnInit: true,
}),
},
compileRelayAdmin: {
generateRelayAdmin: {
paths: [
"core/client/admin/**/*.ts",
"core/client/admin/**/*.tsx",
@@ -44,11 +44,11 @@ const config: Config = {
"**/test/**/*",
"core/**/*.spec.*",
],
executor: new CommandExecutor("npm run compile:relay-admin", {
executor: new CommandExecutor("npm run generate:relay-admin", {
runOnInit: true,
}),
},
compileRelayAuth: {
generateRelayAuth: {
paths: [
"core/client/auth/**/*.ts",
"core/client/auth/**/*.tsx",
@@ -61,11 +61,11 @@ const config: Config = {
"**/test/**/*",
"core/**/*.spec.*",
],
executor: new CommandExecutor("npm run compile:relay-auth", {
executor: new CommandExecutor("npm run generate:relay-auth", {
runOnInit: true,
}),
},
compileRelayInstall: {
generateRelayInstall: {
paths: [
"core/client/install/**/*.ts",
"core/client/install/**/*.tsx",
@@ -78,13 +78,13 @@ const config: Config = {
"**/test/**/*",
"core/**/*.spec.*",
],
executor: new CommandExecutor("npm run compile:relay-install", {
executor: new CommandExecutor("npm run generate:relay-install", {
runOnInit: true,
}),
},
compileCSSTypes: {
generateCSSTypes: {
paths: ["**/*.css"],
executor: new CommandExecutor("npm run compile:css-types", {
executor: new CommandExecutor("npm run generate:css-types", {
runOnInit: true,
}),
},
@@ -104,24 +104,24 @@ const config: Config = {
},
defaultSet: "client",
sets: {
server: ["compileSchema", "runServer"],
server: ["generateSchemaTypes", "runServer"],
client: [
"runServer",
"runWebpackDevServer",
"compileCSSTypes",
"compileRelayStream",
"compileRelayAuth",
"compileRelayInstall",
"compileRelayAdmin",
"compileSchema",
"generateCSSTypes",
"generateRelayStream",
"generateRelayAuth",
"generateRelayInstall",
"generateRelayAdmin",
"generateSchemaTypes",
],
docz: ["runDocz", "compileCSSTypes"],
compile: [
"compileSchema",
"compileCSSTypes",
"compileRelayStream",
"compileRelayAuth",
"compileRelayInstall",
docz: ["runDocz", "generate"],
generate: [
"generateSchemaTypes",
"generateCSSTypes",
"generateRelayStream",
"generateRelayAuth",
"generateRelayInstall",
],
},
};
+167 -29
View File
@@ -2087,6 +2087,15 @@
"integrity": "sha512-iiJbKLZbhSa6FYRip/9ZDX6HXhayXLDGY2Fqws9cOkEQ6XeKfaxB0sC541mowZJueYyMnVUmmG+al5/4fCDrgw==",
"dev": true
},
"@types/lodash-webpack-plugin": {
"version": "0.11.3",
"resolved": "http://registry.npmjs.org/@types/lodash-webpack-plugin/-/lodash-webpack-plugin-0.11.3.tgz",
"integrity": "sha512-zzwcefaw1wAQZsSWa1ChfhgjtoOTVdzoxSnmuRrGlHnAEF2k+NIAzgJlUc2V2RsvlOaE2WT/CM/zW9m+GX8wAw==",
"dev": true,
"requires": {
"@types/webpack": "*"
}
},
"@types/long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz",
@@ -2227,12 +2236,6 @@
"@types/react": "*"
}
},
"@types/query-string": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/query-string/-/query-string-6.1.0.tgz",
"integrity": "sha512-6QxF7V3SkdyBRJ81GheWL9Nzr7uDrCZH0hkxdorNcXeBOeAPN5AUf1n9dpecaBOzxJAckWP4nIOeY1YxlhuMTQ==",
"dev": true
},
"@types/range-parser": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz",
@@ -2421,6 +2424,15 @@
}
}
},
"@types/webpack-bundle-analyzer": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.13.0.tgz",
"integrity": "sha512-+qy5xatScNZW4NbIVaiV38XOeHbKRa4FIPeMf2VDpZEon9W/cxjaVR080vRrRGvfq4tRvOusTEypSMxTvjcSzw==",
"dev": true,
"requires": {
"@types/webpack": "*"
}
},
"@types/webpack-dev-server": {
"version": "2.9.5",
"resolved": "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-2.9.5.tgz",
@@ -3989,6 +4001,35 @@
"integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=",
"dev": true
},
"babel-plugin-lodash": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/babel-plugin-lodash/-/babel-plugin-lodash-3.3.4.tgz",
"integrity": "sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg==",
"dev": true,
"requires": {
"@babel/helper-module-imports": "^7.0.0-beta.49",
"@babel/types": "^7.0.0-beta.49",
"glob": "^7.1.1",
"lodash": "^4.17.10",
"require-package-name": "^2.0.1"
},
"dependencies": {
"glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
}
},
"babel-plugin-macros": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.0.0.tgz",
@@ -5614,6 +5655,18 @@
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
},
"bfj": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.1.tgz",
"integrity": "sha512-+GUNvzHR4nRyGybQc2WpNJL4MJazMuvf92ueIyA0bIkPRwhhQu3IfZQ2PSoVPpCBJfmoSdOxu5rnotfFLlvYRQ==",
"dev": true,
"requires": {
"bluebird": "^3.5.1",
"check-types": "^7.3.0",
"hoopy": "^0.1.2",
"tryer": "^1.0.0"
}
},
"big.js": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz",
@@ -6282,6 +6335,12 @@
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
"dev": true
},
"check-types": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz",
"integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==",
"dev": true
},
"cheerio": {
"version": "1.0.0-rc.2",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz",
@@ -9484,8 +9543,7 @@
"dompurify": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-1.0.8.tgz",
"integrity": "sha512-vetRFbN1SXSPfP3ClIiYnxTrXquSqakBEOoB5JESn0SVcSYzpu6ougjakpKnskGctYdlNpwf+riUHSkG7d4XUw==",
"dev": true
"integrity": "sha512-vetRFbN1SXSPfP3ClIiYnxTrXquSqakBEOoB5JESn0SVcSYzpu6ougjakpKnskGctYdlNpwf+riUHSkG7d4XUw=="
},
"domutils": {
"version": "1.5.1",
@@ -10550,6 +10608,12 @@
}
}
},
"filesize": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
"integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==",
"dev": true
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@@ -12360,6 +12424,16 @@
"glogg": "^1.0.0"
}
},
"gzip-size": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz",
"integrity": "sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA==",
"dev": true,
"requires": {
"duplexer": "^0.1.1",
"pify": "^3.0.0"
}
},
"handle-thing": {
"version": "1.2.5",
"resolved": "http://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
@@ -12607,6 +12681,12 @@
"parse-passwd": "^1.0.0"
}
},
"hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
"integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==",
"dev": true
},
"hosted-git-info": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.1.tgz",
@@ -13175,6 +13255,12 @@
"integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=",
"dev": true
},
"intersection-observer": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.5.1.tgz",
"integrity": "sha512-Zd7Plneq82kiXFixs7bX62YnuZ0BMRci9br7io88LwDyF3V43cQMI+G5IiTlTNTt+LsDUppl19J/M2Fp9UkH6g==",
"dev": true
},
"intl-pluralrules": {
"version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b",
"from": "github:projectfluent/IntlPluralRules#module",
@@ -16326,6 +16412,15 @@
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz",
"integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q=="
},
"lodash-webpack-plugin": {
"version": "0.11.5",
"resolved": "https://registry.npmjs.org/lodash-webpack-plugin/-/lodash-webpack-plugin-0.11.5.tgz",
"integrity": "sha512-QWfEIYxpixOdbd6KBe5g6MDWcyTgP3trDXwKHFqTlXrWiLcs/67fGQ0IWeRyhWlTITQIgMpJAYd2oeIztuV5VA==",
"dev": true,
"requires": {
"lodash": "^4.17.4"
}
},
"lodash._reinterpolate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
@@ -18085,6 +18180,12 @@
"integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=",
"dev": true
},
"opener": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
"integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
"dev": true
},
"opn": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz",
@@ -21129,24 +21230,6 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
},
"query-string": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-6.1.0.tgz",
"integrity": "sha512-pNB/Gr8SA8ff8KpUFM36o/WFAlthgaThka5bV19AD9PNTH20Pwq5Zxodif2YyHwrctp6SkL4GqlOot0qR/wGaw==",
"dev": true,
"requires": {
"decode-uri-component": "^0.2.0",
"strict-uri-encode": "^2.0.0"
},
"dependencies": {
"strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=",
"dev": true
}
}
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
@@ -21160,9 +21243,9 @@
"dev": true
},
"querystringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz",
"integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz",
"integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==",
"dev": true
},
"quick-lru": {
@@ -22762,6 +22845,12 @@
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
"dev": true
},
"require-package-name": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz",
"integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=",
"dev": true
},
"require_optional": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
@@ -24638,6 +24727,12 @@
"integrity": "sha512-FHkoUZvG6Egrv9XZAyYGKEyb1JMsFphgPjoczkZC2y6W93U1jswcVURB8MUvtsahEPEVACyxD47JAL63vF4JsQ==",
"dev": true
},
"tryer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
"integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==",
"dev": true
},
"ts-jest": {
"version": "23.0.0",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.0.0.tgz",
@@ -26013,6 +26108,49 @@
"webpack-sources": "^1.0.1"
}
},
"webpack-bundle-analyzer": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.3.tgz",
"integrity": "sha512-naLWiRfmtH4UJgtUktRTLw6FdoZJ2RvCR9ePbwM9aRMsS/KjFerkPZG9epEvXRAw5d5oPdrs9+3p+afNjxW8Xw==",
"dev": true,
"requires": {
"acorn": "^5.7.3",
"bfj": "^6.1.1",
"chalk": "^2.4.1",
"commander": "^2.18.0",
"ejs": "^2.6.1",
"express": "^4.16.3",
"filesize": "^3.6.1",
"gzip-size": "^5.0.0",
"lodash": "^4.17.10",
"mkdirp": "^0.5.1",
"opener": "^1.5.1",
"ws": "^6.0.0"
},
"dependencies": {
"acorn": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
"integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
"dev": true
},
"commander": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
"dev": true
},
"ws": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz",
"integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==",
"dev": true,
"requires": {
"async-limiter": "~1.0.0"
}
}
}
},
"webpack-chain": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-4.8.0.tgz",
+15 -12
View File
@@ -15,16 +15,16 @@
"contributors": "https://github.com/coralproject/talk/graphs/contributors",
"description": "A better commenting experience from Mozilla, The Washington Post, and The New York Times.",
"scripts": {
"build": "npm-run-all compile --parallel build:*",
"build": "npm-run-all generate --parallel build:*",
"build:client": "ts-node ./scripts/build.ts",
"build:server": "gulp server",
"compile": "npm-run-all --parallel compile:*",
"compile:css-types": "tcm src/core/client/",
"compile:relay-stream": "ts-node ./scripts/compileRelay --src ./src/core/client/stream --schema tenant",
"compile:relay-auth": "ts-node ./scripts/compileRelay --src ./src/core/client/auth --schema tenant",
"compile:relay-install": "ts-node ./scripts/compileRelay --src ./src/core/client/install --schema tenant",
"compile:relay-admin": "ts-node ./scripts/compileRelay --src ./src/core/client/admin --schema tenant",
"compile:schema": "node ./scripts/generateSchemaTypes.js",
"generate": "npm-run-all --parallel generate:*",
"generate:css-types": "tcm src/core/client/",
"generate:relay-stream": "ts-node ./scripts/compileRelay --src ./src/core/client/stream --schema tenant",
"generate:relay-auth": "ts-node ./scripts/compileRelay --src ./src/core/client/auth --schema tenant",
"generate:relay-install": "ts-node ./scripts/compileRelay --src ./src/core/client/install --schema tenant",
"generate:relay-admin": "ts-node ./scripts/compileRelay --src ./src/core/client/admin --schema tenant",
"generate:schema": "node ./scripts/generateSchemaTypes.js",
"docz": "docz",
"start": "node dist/index.js",
"start:development": "ts-node --project ./src/tsconfig.json -r tsconfig-paths/register ./src/index.ts",
@@ -32,14 +32,12 @@
"lint": "npm-run-all --parallel lint:* tscheck:*",
"lint:server": "tslint --project ./src/tsconfig.json",
"lint:client": "tslint --project ./src/core/client/tsconfig.json",
"lint:client-embed": "tslint --project ./src/core/client/embed/tsconfig.json",
"lint:scripts": "tslint --project ./tsconfig.json",
"lint-fix": "npm run lint:server -- --fix && npm run lint:client -- --fix && npm run lint:client-embed -- --fix && npm run lint:scripts -- --fix",
"test": "node scripts/test.js --env=jsdom",
"tscheck": "npm-run-all --parallel tscheck:*",
"tscheck:server": "tsc --project ./src/tsconfig.json --noEmit",
"tscheck:client": "tsc --project ./src/core/client/tsconfig.json --noEmit",
"tscheck:client-embed": "tsc --project ./src/core/client/embed/tsconfig.json --noEmit",
"tscheck:scripts": "tsc --project ./tsconfig.json --noEmit",
"watch": "NODE_ENV=development ts-node ./scripts/watcher/bin/watcher.ts --config ./config/watcher.ts"
},
@@ -145,6 +143,7 @@
"@types/linkify-it": "^2.0.3",
"@types/linkifyjs": "^2.1.0",
"@types/lodash": "^4.14.118",
"@types/lodash-webpack-plugin": "^0.11.3",
"@types/luxon": "^0.5.3",
"@types/mini-css-extract-plugin": "^0.2.0",
"@types/mongodb": "^3.1.14",
@@ -158,7 +157,6 @@
"@types/passport-local": "^1.0.33",
"@types/passport-oauth2": "^1.4.5",
"@types/passport-strategy": "^0.2.33",
"@types/query-string": "^6.1.0",
"@types/react-copy-to-clipboard": "^4.2.5",
"@types/react-dom": "^16.0.6",
"@types/react-relay": "^1.3.9",
@@ -173,6 +171,7 @@
"@types/uuid": "^3.4.3",
"@types/vinyl": "^2.0.2",
"@types/webpack": "^4.4.7",
"@types/webpack-bundle-analyzer": "^2.13.0",
"@types/webpack-dev-server": "^2.9.5",
"@types/webpack-manifest-plugin": "^1.3.2",
"@types/ws": "^5.1.2",
@@ -180,6 +179,7 @@
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.6.0",
"babel-loader": "^8.0.0-beta",
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-module-resolver": "^3.1.1",
"babel-plugin-relay": "^1.7.0-rc.1",
"babel-preset-react-optimize": "^1.0.1",
@@ -214,6 +214,7 @@
"gulp-typescript": "^5.0.0-alpha.3",
"html-webpack-plugin": "^3.2.0",
"husky": "^1.1.0",
"intersection-observer": "^0.5.1",
"jest": "^23.4.1",
"jest-junit": "^5.1.0",
"jest-localstorage-mock": "^2.2.0",
@@ -221,6 +222,7 @@
"jsdom": "^11.12.0",
"lint-staged": "^7.3.0",
"loader-utils": "^1.1.0",
"lodash-webpack-plugin": "^0.11.5",
"material-design-icons": "^3.0.1",
"mini-css-extract-plugin": "^0.4.1",
"npm-run-all": "^4.1.3",
@@ -237,7 +239,7 @@
"prop-types": "^15.6.2",
"pstree.remy": "^1.1.0",
"pym.js": "^1.3.2",
"query-string": "^6.1.0",
"querystringify": "^2.1.0",
"raw-loader": "^0.5.1",
"react": "^16.5.2",
"react-copy-to-clipboard": "^5.0.1",
@@ -278,6 +280,7 @@
"uglifyjs-webpack-plugin": "^1.2.5",
"wait-for-expect": "^1.1.0",
"webpack": "4.12.0",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.0.2",
"webpack-dev-server": "^3.1.10",
"webpack-hot-client": "^4.1.1",
+2
View File
@@ -15,6 +15,8 @@ import config, { createClientEnv } from "../src/core/common/config";
// Enforce environment to be production.
config.validate().set("env", "production");
process.env.WEBPACK = "true";
process.env.BABEL_ENV = "production";
process.env.NODE_ENV = "production";
+2
View File
@@ -17,6 +17,8 @@ import config, { createClientEnv } from "../src/core/common/config";
// Enforce environment to be development.
config.validate().set("env", "development");
process.env.WEBPACK = "true";
process.env.BABEL_ENV = "development";
process.env.NODE_ENV = "development";
+40 -9
View File
@@ -1,17 +1,29 @@
import CaseSensitivePathsPlugin from "case-sensitive-paths-webpack-plugin";
import CompressionPlugin from "compression-webpack-plugin";
import HtmlWebpackPlugin, { Options } from "html-webpack-plugin";
import { identity } from "lodash";
import LodashModuleReplacementPlugin from "lodash-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import path from "path";
import InterpolateHtmlPlugin from "react-dev-utils/InterpolateHtmlPlugin";
import WatchMissingNodeModulesPlugin from "react-dev-utils/WatchMissingNodeModulesPlugin";
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
import UglifyJsPlugin from "uglifyjs-webpack-plugin";
import webpack, { Configuration } from "webpack";
import webpack, { Configuration, Plugin } from "webpack";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
import ManifestPlugin from "webpack-manifest-plugin";
import PublicURIWebpackPlugin from "./plugins/PublicURIWebpackPlugin";
import paths from "./paths";
import PublicURIWebpackPlugin from "./plugins/PublicURIWebpackPlugin";
/**
* filterPlugins will filter out null values from the array of plugins, allowing
* easy embedded ternaries.
*
* @param plugins array of plugins and null values
*/
const filterPlugins = (plugins: Array<Plugin | null>): Plugin[] =>
plugins.filter(identity) as Plugin[];
interface CreateWebpackConfig {
publicPath?: string;
@@ -139,6 +151,9 @@ export default function createWebpackConfig({
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
];
// If the WEBPACK_STATS environment variable is specified, output the stats!
const includeStats = Boolean(process.env.WEBPACK_STATS);
const baseConfig: Configuration = {
// Set webpack mode.
mode: isProduction ? "production" : "development",
@@ -336,6 +351,9 @@ export default function createWebpackConfig({
{
loader: require.resolve("babel-loader"),
options: {
// This will ensure that all packages in node_modules that
// import lodash do so in a way that supports tree shaking.
plugins: ["lodash"],
presets: [
[
"@babel/env",
@@ -399,6 +417,7 @@ export default function createWebpackConfig({
],
},
plugins: [
new LodashModuleReplacementPlugin(),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
new webpack.DefinePlugin(envStringified),
@@ -473,7 +492,7 @@ export default function createWebpackConfig({
paths.appAdminIndex,
],
},
plugins: [
plugins: filterPlugins([
...baseConfig.plugins!,
// Generates an `stream.html` file with the <script> injected.
new HtmlWebpackPlugin({
@@ -527,16 +546,21 @@ export default function createWebpackConfig({
new ManifestPlugin({
fileName: "asset-manifest.json",
}),
],
// If stats are enabled, output them!
includeStats
? new BundleAnalyzerPlugin({
analyzerMode: "static",
reportFilename: "report-assets.html",
})
: null,
]),
},
/* Webpack config for our embed */
{
...baseConfig,
entry: [
/* Use minimal amount of polyfills (for IE) */
"core-js/fn/object/assign",
"core-js/fn/symbol",
"core-js/fn/symbol/iterator",
"intersection-observer", // also for Safari
...devServerEntries,
paths.appEmbedIndex,
],
@@ -547,7 +571,7 @@ export default function createWebpackConfig({
// as this lives in a static template on the embed site.
filename: "assets/js/embed.js",
},
plugins: [
plugins: filterPlugins([
...baseConfig.plugins!,
...(isProduction
? []
@@ -583,7 +607,14 @@ export default function createWebpackConfig({
new ManifestPlugin({
fileName: "embed-manifest.json",
}),
],
// If stats are enabled, output them!
includeStats
? new BundleAnalyzerPlugin({
analyzerMode: "static",
reportFilename: "report-embed.html",
})
: null,
]),
},
];
}
+1 -1
View File
@@ -3,7 +3,7 @@ module.exports = {
["@babel/env", { targets: "last 2 versions, ie 11", modules: false }],
"@babel/react",
],
plugins: ["@babel/syntax-dynamic-import"],
plugins: ["@babel/syntax-dynamic-import", "lodash"],
env: {
production: {
plugins: [],
+2 -2
View File
@@ -1,6 +1,6 @@
import qs from "query-string";
import { commitLocalUpdate, Environment } from "relay-runtime";
import { parseQuery } from "talk-common/utils";
import {
createAndRetain,
LOCAL_ID,
@@ -18,7 +18,7 @@ export default async function initLocalState(environment: Environment) {
const localRecord = createAndRetain(environment, s, LOCAL_ID, LOCAL_TYPE);
// Parse query params
const query = qs.parse(location.search);
const query = parseQuery(location.search);
// Set default view.
localRecord.setValue(query.view || "SIGN_IN", "view");
+95 -45
View File
@@ -1,11 +1,11 @@
import { EventEmitter2 } from "eventemitter2";
import { omit } from "lodash";
import sinon from "sinon";
import sinon, { SinonMock } from "sinon";
import { PymControlConfig } from "./PymControl";
import { StreamEmbed, StreamEmbedConfig } from "./StreamEmbed";
it("should throw when calling pym dependent methods but was not rendered", () => {
it("should throw when calling remove but was not rendered", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
@@ -13,13 +13,7 @@ it("should throw when calling pym dependent methods but was not rendered", () =>
rootURL: "http://localhost/",
};
const streamEmbed = new StreamEmbed(config);
[
() => streamEmbed.login("token"),
() => streamEmbed.logout(),
() => streamEmbed.remove(),
].forEach(cb => {
expect(cb).toThrow();
});
expect(() => streamEmbed.remove()).toThrow();
});
it("should return rendered", () => {
const config: StreamEmbedConfig = {
@@ -65,44 +59,100 @@ it("should relay events methods to event emitter", () => {
streamEmbed.off("event", callback);
});
it("should send login message to PymControl", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
};
const pymControl = {
// tslint:disable-next-line:no-empty
sendMessage: () => {},
};
const pymControlMock = sinon.mock(pymControl);
pymControlMock.expects("sendMessage").withArgs("login", "token");
const fakeFactory: any = () => pymControl;
const streamEmbed = new StreamEmbed(config, fakeFactory);
streamEmbed.render();
streamEmbed.login("token");
pymControlMock.verify();
describe("should send login message to PymControl", () => {
let pymControlMock: SinonMock;
let streamEmbed: StreamEmbed;
let eventEmitter: EventEmitter2;
beforeEach(() => {
eventEmitter = new EventEmitter2();
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter,
id: "container-id",
rootURL: "http://localhost/",
};
const pymControl = {
// tslint:disable-next-line:no-empty
sendMessage: () => {},
};
const fakeFactory: any = () => pymControl;
pymControlMock = sinon.mock(pymControl);
pymControlMock.expects("sendMessage").withArgs("login", "token");
streamEmbed = new StreamEmbed(config, fakeFactory);
});
afterEach(() => {
pymControlMock.restore();
});
it("send login immediately when already ready", () => {
streamEmbed.render();
eventEmitter.emit("ready");
streamEmbed.login("token");
pymControlMock.verify();
});
it("defer login until ready", () => {
streamEmbed.login("token");
streamEmbed.render();
eventEmitter.emit("ready");
pymControlMock.verify();
});
it("do not call login when not ready", () => {
streamEmbed.login("token");
streamEmbed.render();
expect(() => pymControlMock.verify()).toThrow();
});
});
it("should send logout message to PymControl", () => {
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter: new EventEmitter2(),
id: "container-id",
rootURL: "http://localhost/",
};
const pymControl = {
// tslint:disable-next-line:no-empty
sendMessage: () => {},
};
const pymControlMock = sinon.mock(pymControl);
pymControlMock.expects("sendMessage").withArgs("logout");
const fakeFactory: any = () => pymControl;
const streamEmbed = new StreamEmbed(config, fakeFactory);
streamEmbed.render();
streamEmbed.logout();
pymControlMock.verify();
describe("should send logout message to PymControl", () => {
let pymControlMock: SinonMock;
let streamEmbed: StreamEmbed;
let eventEmitter: EventEmitter2;
beforeEach(() => {
eventEmitter = new EventEmitter2();
const config: StreamEmbedConfig = {
title: "StreamEmbed",
eventEmitter,
id: "container-id",
rootURL: "http://localhost/",
};
const pymControl = {
// tslint:disable-next-line:no-empty
sendMessage: () => {},
};
const fakeFactory: any = () => pymControl;
pymControlMock = sinon.mock(pymControl);
pymControlMock.expects("sendMessage").withArgs("logout");
streamEmbed = new StreamEmbed(config, fakeFactory);
});
afterEach(() => {
pymControlMock.restore();
});
it("send logout immediately when already ready", () => {
streamEmbed.render();
eventEmitter.emit("ready");
streamEmbed.logout();
pymControlMock.verify();
});
it("defer logout until ready", () => {
streamEmbed.logout();
streamEmbed.render();
eventEmitter.emit("ready");
pymControlMock.verify();
});
it("do not call logout when not ready", () => {
streamEmbed.logout();
streamEmbed.render();
expect(() => pymControlMock.verify()).toThrow();
});
});
it("should pass default values to pymControl", () => {
+30 -9
View File
@@ -1,6 +1,6 @@
import { EventEmitter2 } from "eventemitter2";
import qs from "query-string";
import { stringifyQuery } from "talk-common/utils";
import ensureNoEndSlash from "talk-common/utils/ensureNoEndSlash";
import urls from "talk-framework/helpers/urls";
import { ExternalConfig } from "talk-framework/lib/externalConfig";
@@ -15,7 +15,7 @@ import {
withPymStorage,
withSetCommentID,
} from "./decorators";
import onIntersect from "./onIntersect";
import onIntersect, { OnIntersectCancellation } from "./onIntersect";
import PymControl, {
defaultPymControlFactory,
PymControlFactory,
@@ -37,6 +37,8 @@ export class StreamEmbed {
private config: StreamEmbedConfig;
private pymControl?: PymControl;
private pymControlFactory: PymControlFactory;
private ready = false;
private cancelAutoRender: OnIntersectCancellation | null = null;
constructor(
config: StreamEmbedConfig,
@@ -53,13 +55,20 @@ export class StreamEmbed {
if (config.commentID) {
this.render();
} else {
onIntersect(document.getElementById(config.id)!, () => {
if (!this.rendered) {
this.render();
this.cancelAutoRender = onIntersect(
document.getElementById(config.id)!,
() => {
this.cancelAutoRender = null;
if (!this.rendered) {
this.render();
}
}
});
);
}
}
config.eventEmitter.once("ready", () => {
this.ready = true;
});
}
private assertRendered() {
@@ -77,16 +86,28 @@ export class StreamEmbed {
}
public login(token: string) {
this.assertRendered();
if (!this.ready) {
this.config.eventEmitter.once("ready", () => this.login(token));
return;
}
this.pymControl!.sendMessage("login", token);
}
public logout() {
this.assertRendered();
if (!this.ready) {
this.config.eventEmitter.once("ready", () => this.logout());
return;
}
this.pymControl!.sendMessage("logout");
}
public remove() {
// If lazy render was enabled, just cancel it.
if (this.cancelAutoRender) {
this.cancelAutoRender();
this.cancelAutoRender = null;
return;
}
this.assertRendered();
this.pymControl!.remove();
this.pymControl = undefined;
@@ -116,7 +137,7 @@ export class StreamEmbed {
withConfig(externalConfig),
];
const query = qs.stringify({
const query = stringifyQuery({
storyID: this.config.storyID,
storyURL: this.config.storyURL,
commentID: this.config.commentID,
+3 -2
View File
@@ -1,5 +1,6 @@
import { EventEmitter2 } from "eventemitter2";
import qs from "query-string";
import { parseQuery } from "talk-common/utils";
import { default as create, StreamEmbed } from "./StreamEmbed";
@@ -41,7 +42,7 @@ function resolveStoryURL() {
export function createStreamEmbed(config: Config): StreamEmbed {
// Parse query params
const query = qs.parse(location.search);
const query = parseQuery(location.search);
const eventEmitter = new EventEmitter2({ wildcard: true });
if (config.events) {
@@ -4,7 +4,7 @@ exports[`should pass correct values to pymControl 1`] = `
Object {
"id": "container-id",
"title": "StreamEmbed",
"url": "http://localhost/embed/stream?commentID=comment-id&storyID=story-id&storyURL=story-url",
"url": "http://localhost/embed/stream?storyID=story-id&storyURL=story-url&commentID=comment-id",
}
`;
@@ -11,7 +11,9 @@ it("should emit events from pym to eventEmitter", () => {
};
const fakePym = {
onMessage: (type: string, callback: (raw: string) => void) => {
expect(type).toBe("event");
if (type !== "event") {
return;
}
callback(JSON.stringify({ eventName: "eventName", value: "value" }));
},
el: document.createElement("div"),
@@ -19,3 +21,23 @@ it("should emit events from pym to eventEmitter", () => {
withEventEmitter(eventEmitterMock as any)(fakePym as any);
eventEmitterMock.emit.verify();
});
it("should emit ready event from pym to eventEmitter", () => {
const eventEmitterMock = {
emit: sinon
.mock()
.once()
.withArgs("ready"),
};
const fakePym = {
onMessage: (type: string, callback: () => void) => {
if (type !== "ready") {
return;
}
callback();
},
el: document.createElement("div"),
};
withEventEmitter(eventEmitterMock as any)(fakePym as any);
eventEmitterMock.emit.verify();
});
@@ -8,6 +8,11 @@ const withEventEmitter = (eventEmitter: EventEmitter2): Decorator => pym => {
const { eventName, value } = JSON.parse(raw);
eventEmitter.emit(eventName, value);
});
// Notify ready state.
pym.onMessage("ready", () => {
eventEmitter.emit("ready");
});
};
export default withEventEmitter;
@@ -1,6 +1,6 @@
import sinon from "sinon";
import { createInMemoryStorage } from "../testUtils";
import { createInMemoryStorage } from "talk-framework/lib/storage";
import withPymStorage from "./withPymStorage";
class PymStub {
@@ -1,17 +1,17 @@
import qs from "query-string";
import { parseQuery, stringifyQuery } from "talk-common/utils";
import { buildURL } from "talk-framework/utils";
import { Decorator } from "./types";
function getCurrentCommentID() {
return qs.parse(location.search).commentID;
return parseQuery(location.search).commentID;
}
const withSetCommentID: Decorator = pym => {
// Add the permalink comment id to the query.
pym.onMessage("setCommentID", (id: string) => {
const search = qs.stringify({
...qs.parse(location.search),
const search = stringifyQuery({
...parseQuery(location.search),
commentID: id || undefined,
});
+7 -7
View File
@@ -1,10 +1,9 @@
export default function onIntersect(el: HTMLElement, callback: () => void) {
if (!IntersectionObserver) {
// tslint:disable-next-line:no-console
console.warn("IntersectionObserver not available");
callback();
return;
}
export type OnIntersectCancellation = () => void;
export default function onIntersect(
el: HTMLElement,
callback: () => void
): OnIntersectCancellation {
const options = {
rootMargin: "100px",
threshold: 1.0,
@@ -17,4 +16,5 @@ export default function onIntersect(el: HTMLElement, callback: () => void) {
}
}, options);
observer.observe(el);
return () => observer.disconnect();
}
@@ -1,34 +0,0 @@
import createInMemoryStorage from "./InMemoryStorage";
it("should set and unset values", () => {
const storage = createInMemoryStorage();
storage.setItem("test", "value");
expect(storage.getItem("test")).toBe("value");
storage.removeItem("test");
expect(storage.getItem("test")).toBeNull();
});
it("should return length", () => {
const storage = createInMemoryStorage();
storage.setItem("a", "value");
storage.setItem("b", "value");
storage.setItem("c", "value");
expect(storage.length).toBe(3);
});
it("should nth key", () => {
const storage = createInMemoryStorage();
storage.setItem("a", "0");
storage.setItem("b", "1");
storage.setItem("c", "2");
expect(storage.key(2)).toBe("c");
});
it("accepts predefined data", () => {
const storage = createInMemoryStorage({
a: "0",
b: "1",
c: "2",
});
expect(storage.toString()).toMatchSnapshot();
});
@@ -1,49 +0,0 @@
/**
* InMemoryStorage is a dumb implementation of the Storage interface that will
* not persist the data at all. It implements the Storage interface found:
*
* https://developer.mozilla.org/en-US/docs/Web/API/Storage
*/
class InMemoryStorage implements Storage {
private data: Record<string, string>;
constructor(data: Record<string, string> = {}) {
this.data = data;
}
get length() {
return Object.keys(this.data).length;
}
public clear() {
this.data = {};
}
public key(n: number) {
if (this.length <= n) {
return null;
}
return Object.keys(this.data)[n];
}
public getItem(key: string) {
return this.data[key] || null;
}
public setItem(key: string, value: string) {
this.data[key] = value;
}
public removeItem(key: string) {
delete this.data[key];
}
public toString() {
return JSON.stringify(this.data);
}
}
export default function createInMemoryStorage(data?: Record<string, string>) {
return new InMemoryStorage(data);
}
@@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`accepts predefined data 1`] = `"{\\"a\\":\\"0\\",\\"b\\":\\"1\\",\\"c\\":\\"2\\"}"`;
-1
View File
@@ -1 +0,0 @@
export { default as createInMemoryStorage } from "./InMemoryStorage";
-18
View File
@@ -1,18 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"lib": ["dom", "es6"],
"types": ["jest", "node"],
"baseUrl": "./",
"paths": {
"talk-common/*": ["../../common/*"],
"talk-framework/*": ["../framework/*"]
}
},
"include": [
"./**/*",
"../../../types/pym.d.ts",
"../../../types/simulant.d.ts"
],
"exclude": ["node_modules"]
}
@@ -1,5 +1,5 @@
import sinon from "sinon";
import { createInMemoryStorage } from "../testUtils";
import { createInMemoryStorage } from "talk-framework/lib/storage";
import prefixStorage from "./prefixStorage";
it("should get nth key", () => {
@@ -0,0 +1,29 @@
import { Child } from "pym.js";
import React from "react";
import withContext from "./withContext";
interface Props {
pym: Child;
}
/**
* SendPymReady will notify the parent pym that
* we are ready and have setup all listeners.
*/
class SendPymReady extends React.Component<Props> {
private sent = false;
public componentDidMount() {
if (!this.sent) {
this.sent = true;
this.props.pym.sendMessage("ready", "");
}
}
public render() {
return null;
}
}
const enhanced = withContext(({ pym }) => ({ pym }))(SendPymReady);
export default enhanced;
@@ -23,6 +23,7 @@ import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
import { generateBundles, LocalesData, negotiateLanguages } from "../i18n";
import { createNetwork, TokenGetter } from "../network";
import { PostMessageService } from "../postMessage";
import SendPymReady from "./SendPymReady";
import { TalkContext, TalkContextProvider } from "./TalkContext";
export type InitLocalState = ((
@@ -159,6 +160,7 @@ function createMangedTalkContextProvider(
return (
<TalkContextProvider value={this.state.context}>
{this.props.children}
{this.state.context.pym && <SendPymReady />}
</TalkContextProvider>
);
}
+1 -1
View File
@@ -1,4 +1,4 @@
import merge from "lodash/merge";
import { merge } from "lodash";
import { Overwrite } from "talk-framework/types";
const buildOptions = (inputOptions: RequestInit = {}) => {
@@ -1,11 +1,11 @@
import qs from "query-string";
import { parseQuery, stringifyQuery } from "talk-common/utils";
import buildURL from "./buildURL";
import parseURL from "./parseURL";
export default function modifyQuery(url: string, params: {}) {
const parsed = parseURL(url);
const query = qs.parse(parsed.search);
parsed.search = qs.stringify({ ...query, ...params });
const query = parseQuery(parsed.search);
parsed.search = stringifyQuery({ ...query, ...params });
return buildURL(parsed);
}
@@ -1,5 +1,9 @@
import qs from "query-string";
import { parseQuery } from "talk-common/utils";
export default function parseQueryHash(hash: string): Record<string, string> {
return qs.parse(hash);
let normalized = hash;
if (normalized[0] === "#") {
normalized = normalized.substr(1);
}
return parseQuery(normalized);
}
@@ -1,8 +1,8 @@
import { shallow } from "enzyme";
import qs from "query-string";
import React from "react";
import { Environment, RecordSource } from "relay-runtime";
import { parseQuery } from "talk-common/utils";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { createRelayEnvironment } from "talk-framework/testHelpers";
@@ -38,7 +38,7 @@ it("Sets comment id", () => {
};
shallow(<OnPymSetCommentID {...props} />);
expect(source.get(LOCAL_ID)!.commentID).toEqual(id);
expect(qs.parse(location.search).commentID).toEqual(id);
expect(parseQuery(location.search).commentID).toEqual(id);
});
it("Sets comment id to null when empty", () => {
@@ -54,5 +54,5 @@ it("Sets comment id to null when empty", () => {
};
shallow(<OnPymSetCommentID {...props} />);
expect(source.get(LOCAL_ID)!.commentID).toEqual(null);
expect(qs.parse(location.search).commentID).toBeUndefined();
expect(parseQuery(location.search).commentID).toBeUndefined();
});
@@ -1,6 +1,6 @@
import qs from "query-string";
import { commitLocalUpdate, Environment } from "relay-runtime";
import { parseQuery } from "talk-common/utils";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { getExternalConfig } from "talk-framework/lib/externalConfig";
import { createAndRetain, initLocalBaseState } from "talk-framework/lib/relay";
@@ -26,7 +26,7 @@ export default async function initLocalState(
const localRecord = root.getLinkedRecord("local")!;
// Parse query params
const query = qs.parse(location.search);
const query = parseQuery(location.search);
if (query.storyID) {
localRecord.setValue(query.storyID, "storyID");
@@ -1,8 +1,8 @@
import qs from "query-string";
import { Environment, RecordSource } from "relay-runtime";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { parseQuery } from "talk-common/utils";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { createRelayEnvironment } from "talk-framework/testHelpers";
@@ -29,7 +29,7 @@ it("Sets comment id", () => {
const id = "comment1-id";
commit(environment, { id }, {} as any);
expect(source.get(LOCAL_ID)!.commentID).toEqual(id);
expect(qs.parse(location.search).commentID).toEqual(id);
expect(parseQuery(location.search).commentID).toEqual(id);
});
it("Should call setCommentID in pym", async () => {
@@ -60,6 +60,6 @@ it("Should call setCommentID in pym with empty id", async () => {
commit(environment, { id: null }, context as any);
await timeout();
expect(source.get(LOCAL_ID)!.commentID).toEqual(null);
expect(qs.parse(location.search).commentID).toBeUndefined();
expect(parseQuery(location.search).commentID).toBeUndefined();
context.pym.sendMessage.verify();
});
+2 -1
View File
@@ -10,6 +10,7 @@
"paths": {
"talk-admin/*": ["./admin/*"],
"talk-auth/*": ["./auth/*"],
"talk-embed/*": ["./embed/*"],
"talk-stream/*": ["./stream/*"],
"talk-framework/*": ["./framework/*"],
"talk-ui/*": ["./ui/*"],
@@ -18,5 +19,5 @@
}
},
"include": ["./**/*", "../../types/**/*.d.ts"],
"exclude": ["node_modules", "./embed"]
"exclude": ["node_modules"]
}
+1
View File
@@ -0,0 +1 @@
module.exports = require("../client/.babelrc.js");
+5 -5
View File
@@ -1,5 +1,5 @@
module.exports = {
presets: [
["@babel/env", { targets: "last 2 versions, ie 11", modules: "commonjs" }],
],
};
if (process.env.WEBPACK === "true") {
module.exports = require("./.babelrc.client.js");
} else {
module.exports = require("./.babelrc.server.js");
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
presets: [
["@babel/env", { targets: "last 2 versions, ie 11", modules: "commonjs" }],
],
};
+2
View File
@@ -5,3 +5,5 @@ export { default as oncePerFrame } from "./oncePerFrame";
export { default as isBeforeDate } from "./isBeforeDate";
export { default as ensureEndSlash } from "./ensureEndSlash";
export { default as ensureNoEndSlash } from "./ensureNoEndSlash";
export { default as parseQuery } from "./parseQuery";
export { default as stringifyQuery } from "./stringifyQuery";
+8
View File
@@ -0,0 +1,8 @@
/**
* From the `querystringify` project:
* The parse method transforms a given query string in to an object.
* Parameters without values are set to empty strings.
* It does not care if your query string is prefixed with a ? or not.
* It just extracts the parts between the = and &:
*/
export { parse as default } from "querystringify";
+24
View File
@@ -0,0 +1,24 @@
import qs from "querystringify";
/**
* From the `querystringify` project:
* This transforms a given object in to a query string.
* By default we return the query string without a ? prefix.
* If you want to prefix it by default simply supply true as second argument.
* If it should be prefixed by something else simply supply a string with the
* prefix value as second argument.
*
* In addition keys that have an undefined value are removed from the query.
*/
export default function stringifyQuery(
obj: object,
prefix?: string | boolean
): string {
const copy: any = { ...obj };
Object.keys(copy).forEach(key => {
if (copy[key] === undefined) {
delete copy[key];
}
});
return qs.stringify(copy, prefix);
}
+1
View File
@@ -0,0 +1 @@
declare module "intersection-observer";
+4
View File
@@ -0,0 +1,4 @@
declare module "querystringify" {
export function parse(query: string): any;
export function stringify(obj: object, prefix?: string | boolean): string;
}