| 1 | import semver from 'semver'; |
| 2 | |
| 3 | /** |
| 4 | * Take a version from the window query string and load a specific |
| 5 | * version of React. |
| 6 | * |
| 7 | * @example |
| 8 | * http://localhost:3000?version=15.4.1 |
| 9 | * (Loads React 15.4.1) |
| 10 | */ |
| 11 | |
| 12 | function parseQuery(qstr) { |
| 13 | var query = {}; |
| 14 | var a = qstr.slice(1).split('&'); |
| 15 | |
| 16 | for (var i = 0; i < a.length; i++) { |
| 17 | var b = a[i].split('='); |
| 18 | query[decodeURIComponent(b[0])] = decodeURIComponent(b[1] || ''); |
| 19 | } |
| 20 | return query; |
| 21 | } |
| 22 | |
| 23 | function loadScript(src) { |
| 24 | let firstScript = document.getElementsByTagName('script')[0]; |
| 25 | let scriptNode; |
| 26 | |
| 27 | return new Promise((resolve, reject) => { |
| 28 | scriptNode = document.createElement('script'); |
| 29 | scriptNode.async = 1; |
| 30 | scriptNode.src = src; |
| 31 | |
| 32 | scriptNode.onload = () => resolve(); |
| 33 | scriptNode.onerror = () => reject(new Error(`failed to load: ${src}`)); |
| 34 | |
| 35 | firstScript.parentNode.insertBefore(scriptNode, firstScript); |
| 36 | }); |
| 37 | } |
| 38 | |
| 39 | function loadModules(SymbolSrcPairs) { |
| 40 | let firstScript = document.getElementsByTagName('script')[0]; |
| 41 | |
| 42 | let imports = ''; |
| 43 | SymbolSrcPairs.map(([symbol, src]) => { |
| 44 | imports += `import ${symbol} from "${src}";\n`; |
| 45 | imports += `window.${symbol} = ${symbol};\n`; |
| 46 | }); |
| 47 | |
| 48 | return new Promise((resolve, reject) => { |
| 49 | const timeout = setTimeout( |
| 50 | () => reject(new Error('Timed out loading react modules over esm')), |
| 51 | 5000 |
| 52 | ); |
| 53 | window.__loaded = () => { |
| 54 | clearTimeout(timeout); |
| 55 | resolve(); |
| 56 | }; |
| 57 | |
| 58 | const moduleScript = document.createElement('script'); |
| 59 | moduleScript.type = 'module'; |
| 60 | moduleScript.textContent = imports + 'window.__loaded();'; |
| 61 | |
| 62 | firstScript.parentNode.insertBefore(moduleScript, firstScript); |
| 63 | }); |
| 64 | } |
| 65 | |
| 66 | function getVersion() { |
| 67 | let query = parseQuery(window.location.search); |
| 68 | return query.version || 'local'; |
| 69 | } |
| 70 | |
| 71 | export function isLocal() { |
| 72 | return getVersion() === 'local'; |
| 73 | } |
| 74 | |
| 75 | export function reactPaths(version = getVersion()) { |
| 76 | let query = parseQuery(window.location.search); |
| 77 | let isProduction = query.production === 'true'; |
| 78 | let environment = isProduction ? 'production.min' : 'development'; |
| 79 | let reactPath = `react.${environment}.js`; |
| 80 | let reactDOMPath = `react-dom.${environment}.js`; |
| 81 | let reactDOMClientPath = `react-dom.${environment}.js`; |
| 82 | let reactDOMServerPath = `react-dom-server.browser.${environment}.js`; |
| 83 | let needsCreateElement = true; |
| 84 | let needsReactDOM = true; |
| 85 | let usingModules = false; |
| 86 | |
| 87 | if (version !== 'local') { |
| 88 | const {major, minor, prerelease} = semver(version); |
| 89 | console.log('semver', semver(version)); |
| 90 | |
| 91 | if (major === 0) { |
| 92 | needsCreateElement = minor >= 12; |
| 93 | needsReactDOM = minor >= 14; |
| 94 | } |
| 95 | |
| 96 | const [preReleaseStage] = prerelease; |
| 97 | // The file structure was updated in 16. This wasn't the case for alphas. |
| 98 | // Load the old module location for anything less than 16 RC |
| 99 | if (major >= 19) { |
| 100 | usingModules = true; |
| 101 | const devQuery = environment === 'development' ? '?dev' : ''; |
| 102 | reactPath = 'https://esm.sh/react@' + version + '/' + devQuery; |
| 103 | reactDOMPath = 'https://esm.sh/react-dom@' + version + '/' + devQuery; |
| 104 | reactDOMClientPath = |
| 105 | 'https://esm.sh/react-dom@' + version + '/client' + devQuery; |
| 106 | reactDOMServerPath = |
| 107 | 'https://esm.sh/react-dom@' + version + '/server.browser' + devQuery; |
| 108 | } else if (major >= 16 && !(minor === 0 && preReleaseStage === 'alpha')) { |
| 109 | reactPath = |
| 110 | 'https://unpkg.com/react@' + |
| 111 | version + |
| 112 | '/umd/react.' + |
| 113 | environment + |
| 114 | '.js'; |
| 115 | reactDOMPath = |
| 116 | 'https://unpkg.com/react-dom@' + |
| 117 | version + |
| 118 | '/umd/react-dom.' + |
| 119 | environment + |
| 120 | '.js'; |
| 121 | reactDOMServerPath = |
| 122 | 'https://unpkg.com/react-dom@' + |
| 123 | version + |
| 124 | '/umd/react-dom-server.browser' + |
| 125 | environment; |
| 126 | } else if (major > 0 || minor > 11) { |
| 127 | reactPath = 'https://unpkg.com/react@' + version + '/dist/react.js'; |
| 128 | reactDOMPath = |
| 129 | 'https://unpkg.com/react-dom@' + version + '/dist/react-dom.js'; |
| 130 | reactDOMServerPath = |
| 131 | 'https://unpkg.com/react-dom@' + version + '/dist/react-dom-server.js'; |
| 132 | } else { |
| 133 | reactPath = |
| 134 | 'https://cdnjs.cloudflare.com/ajax/libs/react/' + version + '/react.js'; |
| 135 | } |
| 136 | } else { |
| 137 | throw new Error( |
| 138 | 'This fixture no longer works with local versions. Provide a version query parameter that matches a version published to npm to use the fixture.' |
| 139 | ); |
| 140 | } |
| 141 | |
| 142 | return { |
| 143 | reactPath, |
| 144 | reactDOMPath, |
| 145 | reactDOMClientPath, |
| 146 | reactDOMServerPath, |
| 147 | needsCreateElement, |
| 148 | needsReactDOM, |
| 149 | usingModules, |
| 150 | }; |
| 151 | } |
| 152 | |
| 153 | export default function loadReact() { |
| 154 | console.log('reactPaths', reactPaths()); |
| 155 | const { |
| 156 | reactPath, |
| 157 | reactDOMPath, |
| 158 | reactDOMClientPath, |
| 159 | needsReactDOM, |
| 160 | usingModules, |
| 161 | } = reactPaths(); |
| 162 | |
| 163 | if (usingModules) { |
| 164 | return loadModules([ |
| 165 | ['React', reactPath], |
| 166 | ['ReactDOM', reactDOMPath], |
| 167 | ['ReactDOMClient', reactDOMClientPath], |
| 168 | ]); |
| 169 | } else { |
| 170 | let request = loadScript(reactPath, usingModules); |
| 171 | |
| 172 | if (needsReactDOM) { |
| 173 | request = request.then(() => loadScript(reactDOMPath, usingModules)); |
| 174 | } else { |
| 175 | // Aliasing React to ReactDOM for compatibility. |
| 176 | request = request.then(() => { |
| 177 | window.ReactDOM = window.React; |
| 178 | }); |
| 179 | } |
| 180 | return request; |
| 181 | } |
| 182 | } |