@jsenv/core 28.1.0 → 28.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,45 +1,35 @@
1
- let previousExecutionPromise; // https://twitter.com/damienmaillard/status/1554752482273787906
2
-
1
+ // https://twitter.com/damienmaillard/status/1554752482273787906
3
2
  const isWebkitOrSafari = typeof window.webkitConvertPointFromNodeToPage === "function";
4
3
  const superviseScriptTypeModule = async ({
5
4
  src,
6
5
  async
7
6
  }) => {
7
+ const currentScript = document.querySelector(`script[type="module"][inlined-from-src="${src}"]`);
8
8
  const execute = isWebkitOrSafari ? createExecuteWithDynamicImport({
9
9
  src
10
10
  }) : createExecuteWithScript({
11
+ currentScript,
11
12
  src
12
13
  });
13
14
 
14
- const startExecution = () => {
15
- const execution = window.__supervisor__.createExecution({
16
- src,
17
- type: "js_module",
18
- execute
19
- });
20
-
21
- return execution.start();
22
- };
23
-
24
- if (async) {
25
- startExecution();
26
- return;
27
- } // there is guaranteed execution order for non async script type="module"
28
- // see https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8afd7a6#typemodule-vs-non-module-typetextjavascript-vs-script-nomodule
29
-
30
-
31
- if (previousExecutionPromise) {
32
- await previousExecutionPromise;
33
- previousExecutionPromise = null;
15
+ if (!async) {
16
+ await window.__supervisor__.getPreviousExecutionDonePromise();
34
17
  }
35
18
 
36
- previousExecutionPromise = startExecution();
19
+ const execution = window.__supervisor__.createExecution({
20
+ src,
21
+ async,
22
+ type: "js_module",
23
+ execute
24
+ });
25
+
26
+ return execution.start();
37
27
  };
38
28
 
39
29
  const createExecuteWithScript = ({
30
+ currentScript,
40
31
  src
41
32
  }) => {
42
- const currentScript = document.querySelector(`script[type="module"][inlined-from-src="${src}"]`);
43
33
  const parentNode = currentScript.parentNode;
44
34
  let nodeToReplace;
45
35
  let currentScriptClone;
@@ -48,7 +38,10 @@ const createExecuteWithScript = ({
48
38
  }) => {
49
39
  const urlObject = new URL(src, window.location);
50
40
  const loadPromise = new Promise((resolve, reject) => {
51
- currentScriptClone = document.createElement("script");
41
+ currentScriptClone = document.createElement("script"); // browsers set async by default when creating script(s)
42
+ // we want an exact copy to preserves how code is executed
43
+
44
+ currentScriptClone.async = false;
52
45
  Array.from(currentScript.attributes).forEach(attribute => {
53
46
  currentScriptClone.setAttribute(attribute.nodeName, attribute.nodeValue);
54
47
  });
@@ -729,10 +729,11 @@ window.__supervisor__ = (() => {
729
729
 
730
730
  const supervisedScripts = []
731
731
  const executionPromises = []
732
- supervisor.createExecution = ({ type, src, execute }) => {
732
+ supervisor.createExecution = ({ type, src, async, execute }) => {
733
733
  const execution = {
734
734
  type,
735
735
  src,
736
+ async,
736
737
  execute,
737
738
  }
738
739
  execution.start = () => {
@@ -758,10 +759,17 @@ window.__supervisor__ = (() => {
758
759
  coverage: null,
759
760
  }
760
761
  executionResults[execution.src] = executionResult
761
- let resolvePromise
762
+ let resolveExecutionPromise
762
763
  const promise = new Promise((resolve) => {
763
- resolvePromise = resolve
764
+ resolveExecutionPromise = () => {
765
+ const index = executionPromises.indexOf(promise)
766
+ if (index > -1) {
767
+ executionPromises.splice(index, 1)
768
+ }
769
+ resolve()
770
+ }
764
771
  })
772
+ promise.execution = execution
765
773
  executionPromises.push(promise)
766
774
  try {
767
775
  const result = await execution.execute({ isReload })
@@ -775,7 +783,7 @@ window.__supervisor__ = (() => {
775
783
  console.log(`${execution.type} load ended`)
776
784
  console.groupEnd()
777
785
  }
778
- resolvePromise()
786
+ resolveExecutionPromise()
779
787
  } catch (e) {
780
788
  if (measurePerf) {
781
789
  performance.measure(`execution`, `execution_start`)
@@ -792,73 +800,79 @@ window.__supervisor__ = (() => {
792
800
  if (logs) {
793
801
  console.groupEnd()
794
802
  }
795
- resolvePromise()
803
+ resolveExecutionPromise()
796
804
  }
797
805
  }
798
- let previousExecutionPromise
806
+
807
+ // respect execution order
808
+ // - wait for classic scripts to be done (non async)
809
+ // - wait module script previous execution (non async)
810
+ // see https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8afd7a6#typemodule-vs-non-module-typetextjavascript-vs-script-nomodule
811
+ supervisor.getPreviousExecutionDonePromise = async () => {
812
+ const previousNonAsyncExecutions = executionPromises.filter(
813
+ (promise) => !promise.execution.async,
814
+ )
815
+ await Promise.all(previousNonAsyncExecutions)
816
+ }
799
817
  supervisor.superviseScript = async ({ src, async }) => {
800
818
  const { currentScript } = document
801
819
  const parentNode = currentScript.parentNode
802
- const startExecution = () => {
803
- let nodeToReplace
804
- let currentScriptClone
805
- const execution = supervisor.createExecution({
806
- src,
807
- type: "js_classic",
808
- execute: async ({ isReload }) => {
809
- const urlObject = new URL(src, window.location)
810
- const loadPromise = new Promise((resolve, reject) => {
811
- // do not use script.cloneNode()
812
- // bcause https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
813
- currentScriptClone = document.createElement("script")
814
- Array.from(currentScript.attributes).forEach((attribute) => {
815
- currentScriptClone.setAttribute(
816
- attribute.nodeName,
817
- attribute.nodeValue,
818
- )
819
- })
820
- if (isReload) {
821
- urlObject.searchParams.set("hmr", Date.now())
822
- nodeToReplace = currentScriptClone
823
- currentScriptClone.src = urlObject.href
824
- } else {
825
- currentScriptClone.removeAttribute("jsenv-plugin-owner")
826
- currentScriptClone.removeAttribute("jsenv-plugin-action")
827
- currentScriptClone.removeAttribute("inlined-from-src")
828
- currentScriptClone.removeAttribute("original-position")
829
- currentScriptClone.removeAttribute("original-src-position")
830
- nodeToReplace = currentScript
831
- currentScriptClone.src = src
832
- }
833
- currentScriptClone.addEventListener("error", reject)
834
- currentScriptClone.addEventListener("load", resolve)
835
- parentNode.replaceChild(currentScriptClone, nodeToReplace)
820
+ if (!async) {
821
+ await supervisor.getPreviousExecutionDonePromise()
822
+ }
823
+ let nodeToReplace
824
+ let currentScriptClone
825
+ const execution = supervisor.createExecution({
826
+ src,
827
+ type: "js_classic",
828
+ async,
829
+ execute: async ({ isReload }) => {
830
+ const urlObject = new URL(src, window.location)
831
+ const loadPromise = new Promise((resolve, reject) => {
832
+ // do not use script.cloneNode()
833
+ // bcause https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
834
+ currentScriptClone = document.createElement("script")
835
+ // browsers set async by default when creating script(s)
836
+ // we want an exact copy to preserves how code is executed
837
+ currentScriptClone.async = false
838
+ Array.from(currentScript.attributes).forEach((attribute) => {
839
+ currentScriptClone.setAttribute(
840
+ attribute.nodeName,
841
+ attribute.nodeValue,
842
+ )
836
843
  })
837
- try {
838
- await loadPromise
839
- } catch (e) {
840
- // eslint-disable-next-line no-throw-literal
841
- throw {
842
- message: `Failed to fetch script: ${urlObject.href}`,
843
- reportedBy: "script_error_event",
844
- url: urlObject.href,
845
- // window.error won't be dispatched for this error
846
- needsReport: true,
847
- }
844
+ if (isReload) {
845
+ urlObject.searchParams.set("hmr", Date.now())
846
+ nodeToReplace = currentScriptClone
847
+ currentScriptClone.src = urlObject.href
848
+ } else {
849
+ currentScriptClone.removeAttribute("jsenv-plugin-owner")
850
+ currentScriptClone.removeAttribute("jsenv-plugin-action")
851
+ currentScriptClone.removeAttribute("inlined-from-src")
852
+ currentScriptClone.removeAttribute("original-position")
853
+ currentScriptClone.removeAttribute("original-src-position")
854
+ nodeToReplace = currentScript
855
+ currentScriptClone.src = src
848
856
  }
849
- },
850
- })
851
- return execution.start()
852
- }
853
- if (async) {
854
- startExecution()
855
- return
856
- }
857
- if (previousExecutionPromise) {
858
- await previousExecutionPromise
859
- previousExecutionPromise = null
860
- }
861
- previousExecutionPromise = startExecution()
857
+ currentScriptClone.addEventListener("error", reject)
858
+ currentScriptClone.addEventListener("load", resolve)
859
+ parentNode.replaceChild(currentScriptClone, nodeToReplace)
860
+ })
861
+ try {
862
+ await loadPromise
863
+ } catch (e) {
864
+ // eslint-disable-next-line no-throw-literal
865
+ throw {
866
+ message: `Failed to fetch script: ${urlObject.href}`,
867
+ reportedBy: "script_error_event",
868
+ url: urlObject.href,
869
+ // window.error won't be dispatched for this error
870
+ needsReport: true,
871
+ }
872
+ }
873
+ },
874
+ })
875
+ return execution.start()
862
876
  }
863
877
  supervisor.reloadSupervisedScript = ({ type, src }) => {
864
878
  const supervisedScript = supervisedScripts.find(
@@ -893,9 +907,7 @@ window.__supervisor__ = (() => {
893
907
 
894
908
  const waitPendingExecutions = async () => {
895
909
  if (executionPromises.length) {
896
- const promisesToWait = executionPromises.slice()
897
- executionPromises.length = 0
898
- await Promise.all(promisesToWait)
910
+ await Promise.all(executionPromises)
899
911
  await waitPendingExecutions()
900
912
  }
901
913
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "28.1.0",
3
+ "version": "28.1.1",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -1,46 +1,37 @@
1
- let previousExecutionPromise
2
-
3
1
  // https://twitter.com/damienmaillard/status/1554752482273787906
4
2
  const isWebkitOrSafari =
5
3
  typeof window.webkitConvertPointFromNodeToPage === "function"
6
4
 
7
5
  export const superviseScriptTypeModule = async ({ src, async }) => {
6
+ const currentScript = document.querySelector(
7
+ `script[type="module"][inlined-from-src="${src}"]`,
8
+ )
8
9
  const execute = isWebkitOrSafari
9
10
  ? createExecuteWithDynamicImport({ src })
10
- : createExecuteWithScript({ src })
11
- const startExecution = () => {
12
- const execution = window.__supervisor__.createExecution({
13
- src,
14
- type: "js_module",
15
- execute,
16
- })
17
- return execution.start()
18
- }
19
- if (async) {
20
- startExecution()
21
- return
11
+ : createExecuteWithScript({ currentScript, src })
12
+ if (!async) {
13
+ await window.__supervisor__.getPreviousExecutionDonePromise()
22
14
  }
23
- // there is guaranteed execution order for non async script type="module"
24
- // see https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8afd7a6#typemodule-vs-non-module-typetextjavascript-vs-script-nomodule
25
- if (previousExecutionPromise) {
26
- await previousExecutionPromise
27
- previousExecutionPromise = null
28
- }
29
- previousExecutionPromise = startExecution()
15
+ const execution = window.__supervisor__.createExecution({
16
+ src,
17
+ async,
18
+ type: "js_module",
19
+ execute,
20
+ })
21
+ return execution.start()
30
22
  }
31
23
 
32
- const createExecuteWithScript = ({ src }) => {
33
- const currentScript = document.querySelector(
34
- `script[type="module"][inlined-from-src="${src}"]`,
35
- )
24
+ const createExecuteWithScript = ({ currentScript, src }) => {
36
25
  const parentNode = currentScript.parentNode
37
26
  let nodeToReplace
38
27
  let currentScriptClone
39
-
40
28
  return async ({ isReload }) => {
41
29
  const urlObject = new URL(src, window.location)
42
30
  const loadPromise = new Promise((resolve, reject) => {
43
31
  currentScriptClone = document.createElement("script")
32
+ // browsers set async by default when creating script(s)
33
+ // we want an exact copy to preserves how code is executed
34
+ currentScriptClone.async = false
44
35
  Array.from(currentScript.attributes).forEach((attribute) => {
45
36
  currentScriptClone.setAttribute(attribute.nodeName, attribute.nodeValue)
46
37
  })
@@ -729,10 +729,11 @@ window.__supervisor__ = (() => {
729
729
 
730
730
  const supervisedScripts = []
731
731
  const executionPromises = []
732
- supervisor.createExecution = ({ type, src, execute }) => {
732
+ supervisor.createExecution = ({ type, src, async, execute }) => {
733
733
  const execution = {
734
734
  type,
735
735
  src,
736
+ async,
736
737
  execute,
737
738
  }
738
739
  execution.start = () => {
@@ -758,10 +759,17 @@ window.__supervisor__ = (() => {
758
759
  coverage: null,
759
760
  }
760
761
  executionResults[execution.src] = executionResult
761
- let resolvePromise
762
+ let resolveExecutionPromise
762
763
  const promise = new Promise((resolve) => {
763
- resolvePromise = resolve
764
+ resolveExecutionPromise = () => {
765
+ const index = executionPromises.indexOf(promise)
766
+ if (index > -1) {
767
+ executionPromises.splice(index, 1)
768
+ }
769
+ resolve()
770
+ }
764
771
  })
772
+ promise.execution = execution
765
773
  executionPromises.push(promise)
766
774
  try {
767
775
  const result = await execution.execute({ isReload })
@@ -775,7 +783,7 @@ window.__supervisor__ = (() => {
775
783
  console.log(`${execution.type} load ended`)
776
784
  console.groupEnd()
777
785
  }
778
- resolvePromise()
786
+ resolveExecutionPromise()
779
787
  } catch (e) {
780
788
  if (measurePerf) {
781
789
  performance.measure(`execution`, `execution_start`)
@@ -792,73 +800,79 @@ window.__supervisor__ = (() => {
792
800
  if (logs) {
793
801
  console.groupEnd()
794
802
  }
795
- resolvePromise()
803
+ resolveExecutionPromise()
796
804
  }
797
805
  }
798
- let previousExecutionPromise
806
+
807
+ // respect execution order
808
+ // - wait for classic scripts to be done (non async)
809
+ // - wait module script previous execution (non async)
810
+ // see https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8afd7a6#typemodule-vs-non-module-typetextjavascript-vs-script-nomodule
811
+ supervisor.getPreviousExecutionDonePromise = async () => {
812
+ const previousNonAsyncExecutions = executionPromises.filter(
813
+ (promise) => !promise.execution.async,
814
+ )
815
+ await Promise.all(previousNonAsyncExecutions)
816
+ }
799
817
  supervisor.superviseScript = async ({ src, async }) => {
800
818
  const { currentScript } = document
801
819
  const parentNode = currentScript.parentNode
802
- const startExecution = () => {
803
- let nodeToReplace
804
- let currentScriptClone
805
- const execution = supervisor.createExecution({
806
- src,
807
- type: "js_classic",
808
- execute: async ({ isReload }) => {
809
- const urlObject = new URL(src, window.location)
810
- const loadPromise = new Promise((resolve, reject) => {
811
- // do not use script.cloneNode()
812
- // bcause https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
813
- currentScriptClone = document.createElement("script")
814
- Array.from(currentScript.attributes).forEach((attribute) => {
815
- currentScriptClone.setAttribute(
816
- attribute.nodeName,
817
- attribute.nodeValue,
818
- )
819
- })
820
- if (isReload) {
821
- urlObject.searchParams.set("hmr", Date.now())
822
- nodeToReplace = currentScriptClone
823
- currentScriptClone.src = urlObject.href
824
- } else {
825
- currentScriptClone.removeAttribute("jsenv-plugin-owner")
826
- currentScriptClone.removeAttribute("jsenv-plugin-action")
827
- currentScriptClone.removeAttribute("inlined-from-src")
828
- currentScriptClone.removeAttribute("original-position")
829
- currentScriptClone.removeAttribute("original-src-position")
830
- nodeToReplace = currentScript
831
- currentScriptClone.src = src
832
- }
833
- currentScriptClone.addEventListener("error", reject)
834
- currentScriptClone.addEventListener("load", resolve)
835
- parentNode.replaceChild(currentScriptClone, nodeToReplace)
820
+ if (!async) {
821
+ await supervisor.getPreviousExecutionDonePromise()
822
+ }
823
+ let nodeToReplace
824
+ let currentScriptClone
825
+ const execution = supervisor.createExecution({
826
+ src,
827
+ type: "js_classic",
828
+ async,
829
+ execute: async ({ isReload }) => {
830
+ const urlObject = new URL(src, window.location)
831
+ const loadPromise = new Promise((resolve, reject) => {
832
+ // do not use script.cloneNode()
833
+ // bcause https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
834
+ currentScriptClone = document.createElement("script")
835
+ // browsers set async by default when creating script(s)
836
+ // we want an exact copy to preserves how code is executed
837
+ currentScriptClone.async = false
838
+ Array.from(currentScript.attributes).forEach((attribute) => {
839
+ currentScriptClone.setAttribute(
840
+ attribute.nodeName,
841
+ attribute.nodeValue,
842
+ )
836
843
  })
837
- try {
838
- await loadPromise
839
- } catch (e) {
840
- // eslint-disable-next-line no-throw-literal
841
- throw {
842
- message: `Failed to fetch script: ${urlObject.href}`,
843
- reportedBy: "script_error_event",
844
- url: urlObject.href,
845
- // window.error won't be dispatched for this error
846
- needsReport: true,
847
- }
844
+ if (isReload) {
845
+ urlObject.searchParams.set("hmr", Date.now())
846
+ nodeToReplace = currentScriptClone
847
+ currentScriptClone.src = urlObject.href
848
+ } else {
849
+ currentScriptClone.removeAttribute("jsenv-plugin-owner")
850
+ currentScriptClone.removeAttribute("jsenv-plugin-action")
851
+ currentScriptClone.removeAttribute("inlined-from-src")
852
+ currentScriptClone.removeAttribute("original-position")
853
+ currentScriptClone.removeAttribute("original-src-position")
854
+ nodeToReplace = currentScript
855
+ currentScriptClone.src = src
848
856
  }
849
- },
850
- })
851
- return execution.start()
852
- }
853
- if (async) {
854
- startExecution()
855
- return
856
- }
857
- if (previousExecutionPromise) {
858
- await previousExecutionPromise
859
- previousExecutionPromise = null
860
- }
861
- previousExecutionPromise = startExecution()
857
+ currentScriptClone.addEventListener("error", reject)
858
+ currentScriptClone.addEventListener("load", resolve)
859
+ parentNode.replaceChild(currentScriptClone, nodeToReplace)
860
+ })
861
+ try {
862
+ await loadPromise
863
+ } catch (e) {
864
+ // eslint-disable-next-line no-throw-literal
865
+ throw {
866
+ message: `Failed to fetch script: ${urlObject.href}`,
867
+ reportedBy: "script_error_event",
868
+ url: urlObject.href,
869
+ // window.error won't be dispatched for this error
870
+ needsReport: true,
871
+ }
872
+ }
873
+ },
874
+ })
875
+ return execution.start()
862
876
  }
863
877
  supervisor.reloadSupervisedScript = ({ type, src }) => {
864
878
  const supervisedScript = supervisedScripts.find(
@@ -893,9 +907,7 @@ window.__supervisor__ = (() => {
893
907
 
894
908
  const waitPendingExecutions = async () => {
895
909
  if (executionPromises.length) {
896
- const promisesToWait = executionPromises.slice()
897
- executionPromises.length = 0
898
- await Promise.all(promisesToWait)
910
+ await Promise.all(executionPromises)
899
911
  await waitPendingExecutions()
900
912
  }
901
913
  }