diff --git a/frontend/build/webpack.config.js b/frontend/build/webpack.config.js index feae43f1b..e0ec27c27 100644 --- a/frontend/build/webpack.config.js +++ b/frontend/build/webpack.config.js @@ -36,7 +36,7 @@ module.exports = (env) => { }, entry: { - index: 'index.js' + index: 'index.ts' }, resolve: { @@ -98,7 +98,8 @@ module.exports = (env) => { new HtmlWebpackPlugin({ template: 'frontend/src/index.ejs', filename: 'index.html', - publicPath: '/' + publicPath: '/', + inject: false }), new FileManagerPlugin({ diff --git a/frontend/src/bootstrap.tsx b/frontend/src/bootstrap.tsx new file mode 100644 index 000000000..a729cb3c5 --- /dev/null +++ b/frontend/src/bootstrap.tsx @@ -0,0 +1,23 @@ +import { createBrowserHistory } from 'history'; +import React from 'react'; +import { render } from 'react-dom'; +import createAppStore from 'Store/createAppStore'; +import { fetchTranslations } from 'Utilities/String/translate'; +import App from './App/App'; + +import 'Diag/ConsoleApi'; + +export async function bootstrap() { + const history = createBrowserHistory(); + const store = createAppStore(history); + const hasTranslationsError = !(await fetchTranslations()); + + render( + , + document.getElementById('root') + ); +} diff --git a/frontend/src/index.ejs b/frontend/src/index.ejs index 3abfdf281..b99a39a0d 100644 --- a/frontend/src/index.ejs +++ b/frontend/src/index.ejs @@ -48,7 +48,15 @@ /> - + + + + <% for (key in htmlWebpackPlugin.files.js) { %><% } %> + <% for (key in htmlWebpackPlugin.files.css) { %><% } %> Lidarr @@ -77,7 +85,4 @@
- - - diff --git a/frontend/src/index.js b/frontend/src/index.js deleted file mode 100644 index cb9240c7e..000000000 --- a/frontend/src/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import { createBrowserHistory } from 'history'; -import React from 'react'; -import { render } from 'react-dom'; -import { fetchTranslations } from 'Utilities/String/translate'; - -import './preload'; -import './polyfills'; -import 'Diag/ConsoleApi'; -import 'Styles/globals.css'; -import './index.css'; - -const history = createBrowserHistory(); -const hasTranslationsError = !await fetchTranslations(); - -const { default: createAppStore } = await import('Store/createAppStore'); -const { default: App } = await import('./App/App'); - -const store = createAppStore(history); - -render( - , - document.getElementById('root') -); diff --git a/frontend/src/index.ts b/frontend/src/index.ts new file mode 100644 index 000000000..36aed4c4b --- /dev/null +++ b/frontend/src/index.ts @@ -0,0 +1,19 @@ +import './polyfills'; +import 'Styles/globals.css'; +import './index.css'; + +const initializeUrl = `${ + window.Lidarr.urlBase +}/initialize.json?t=${Date.now()}`; +const response = await fetch(initializeUrl); + +window.Lidarr = await response.json(); + +/* eslint-disable no-undef, @typescript-eslint/ban-ts-comment */ +// @ts-ignore 2304 +__webpack_public_path__ = `${window.Lidarr.urlBase}/`; +/* eslint-enable no-undef, @typescript-eslint/ban-ts-comment */ + +const { bootstrap } = await import('./bootstrap'); + +await bootstrap(); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index dfddb15a3..4ff9d4e87 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,11 +1,11 @@ { "compilerOptions": { - "target": "es6", + "target": "esnext", "allowJs": true, "checkJs": false, "baseUrl": "src", "jsx": "react", - "module": "commonjs", + "module": "esnext", "moduleResolution": "node", "noEmit": true, "esModuleInterop": true, diff --git a/package.json b/package.json index a48c8a182..5e40fe3dd 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "author": "Team Lidarr", "license": "GPL-3.0", "readmeFilename": "readme.md", - "main": "index.js", + "main": "index.ts", "browserslist": [ "defaults" ], diff --git a/src/Lidarr.Http/Frontend/InitializeJsController.cs b/src/Lidarr.Http/Frontend/InitializeJsController.cs deleted file mode 100644 index cab1ccc36..000000000 --- a/src/Lidarr.Http/Frontend/InitializeJsController.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Text; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using NzbDrone.Common; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Core.Analytics; -using NzbDrone.Core.Configuration; - -namespace Lidarr.Http.Frontend -{ - [Authorize(Policy = "UI")] - [ApiController] - public class InitializeJsController : Controller - { - private readonly IConfigFileProvider _configFileProvider; - private readonly IAnalyticsService _analyticsService; - - private static string _apiKey; - private static string _urlBase; - private string _generatedContent; - - public InitializeJsController(IConfigFileProvider configFileProvider, - IAnalyticsService analyticsService) - { - _configFileProvider = configFileProvider; - _analyticsService = analyticsService; - - _apiKey = configFileProvider.ApiKey; - _urlBase = configFileProvider.UrlBase; - } - - [HttpGet("/initialize.js")] - public IActionResult Index() - { - // TODO: Move away from window.Lidarr and prefetch the information returned here when starting the UI - return Content(GetContent(), "application/javascript"); - } - - private string GetContent() - { - if (RuntimeInfo.IsProduction && _generatedContent != null) - { - return _generatedContent; - } - - var builder = new StringBuilder(); - builder.AppendLine("window.Lidarr = {"); - builder.AppendLine($" apiRoot: '{_urlBase}/api/v1',"); - builder.AppendLine($" apiKey: '{_apiKey}',"); - builder.AppendLine($" release: '{BuildInfo.Release}',"); - builder.AppendLine($" version: '{BuildInfo.Version.ToString()}',"); - builder.AppendLine($" instanceName: '{_configFileProvider.InstanceName.ToString()}',"); - builder.AppendLine($" theme: '{_configFileProvider.Theme.ToString()}',"); - builder.AppendLine($" branch: '{_configFileProvider.Branch.ToLower()}',"); - builder.AppendLine($" analytics: {_analyticsService.IsEnabled.ToString().ToLowerInvariant()},"); - builder.AppendLine($" userHash: '{HashUtil.AnonymousToken()}',"); - builder.AppendLine($" urlBase: '{_urlBase}',"); - builder.AppendLine($" isProduction: {RuntimeInfo.IsProduction.ToString().ToLowerInvariant()}"); - builder.AppendLine("};"); - - _generatedContent = builder.ToString(); - - return _generatedContent; - } - } -} diff --git a/src/Lidarr.Http/Frontend/InitializeJsonController.cs b/src/Lidarr.Http/Frontend/InitializeJsonController.cs new file mode 100644 index 000000000..85c611f7b --- /dev/null +++ b/src/Lidarr.Http/Frontend/InitializeJsonController.cs @@ -0,0 +1,66 @@ +using System.Text; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Core.Analytics; +using NzbDrone.Core.Configuration; + +namespace Lidarr.Http.Frontend +{ + [Authorize(Policy = "UI")] + [ApiController] + [ApiExplorerSettings(IgnoreApi = true)] + public class InitializeJsonController : Controller + { + private readonly IConfigFileProvider _configFileProvider; + private readonly IAnalyticsService _analyticsService; + + private static string _apiKey; + private static string _urlBase; + private string _generatedContent; + + public InitializeJsonController(IConfigFileProvider configFileProvider, + IAnalyticsService analyticsService) + { + _configFileProvider = configFileProvider; + _analyticsService = analyticsService; + + _apiKey = configFileProvider.ApiKey; + _urlBase = configFileProvider.UrlBase; + } + + [HttpGet("/initialize.json")] + public IActionResult Index() + { + return Content(GetContent(), "application/json"); + } + + private string GetContent() + { + if (RuntimeInfo.IsProduction && _generatedContent != null) + { + return _generatedContent; + } + + var builder = new StringBuilder(); + builder.AppendLine("{"); + builder.AppendLine($" \"apiRoot\": \"{_urlBase}/api/v1\","); + builder.AppendLine($" \"apiKey\": \"{_apiKey}\","); + builder.AppendLine($" \"release\": \"{BuildInfo.Release}\","); + builder.AppendLine($" \"version\": \"{BuildInfo.Version.ToString()}\","); + builder.AppendLine($" \"instanceName\": \"{_configFileProvider.InstanceName.ToString()}\","); + builder.AppendLine($" \"theme\": \"{_configFileProvider.Theme.ToString()}\","); + builder.AppendLine($" \"branch\": \"{_configFileProvider.Branch.ToLower()}\","); + builder.AppendLine($" \"analytics\": {_analyticsService.IsEnabled.ToString().ToLowerInvariant()},"); + builder.AppendLine($" \"userHash\": \"{HashUtil.AnonymousToken()}\","); + builder.AppendLine($" \"urlBase\": \"{_urlBase}\","); + builder.AppendLine($" \"isProduction\": {RuntimeInfo.IsProduction.ToString().ToLowerInvariant()}"); + builder.AppendLine("}"); + + _generatedContent = builder.ToString(); + + return _generatedContent; + } + } +} diff --git a/src/Lidarr.Http/Frontend/Mappers/HtmlMapperBase.cs b/src/Lidarr.Http/Frontend/Mappers/HtmlMapperBase.cs index 1ad8a787f..f8df01da5 100644 --- a/src/Lidarr.Http/Frontend/Mappers/HtmlMapperBase.cs +++ b/src/Lidarr.Http/Frontend/Mappers/HtmlMapperBase.cs @@ -62,9 +62,11 @@ namespace Lidarr.Http.Frontend.Mappers url = cacheBreakProvider.AddCacheBreakerToPath(match.Groups["path"].Value); } - return string.Format("{0}=\"{1}{2}\"", match.Groups["attribute"].Value, UrlBase, url); + return $"{match.Groups["attribute"].Value}=\"{UrlBase}{url}\""; }); + text = text.Replace("__URL_BASE__", UrlBase); + _generatedContent = text; return _generatedContent; diff --git a/src/Lidarr.Http/Frontend/Mappers/StaticResourceMapper.cs b/src/Lidarr.Http/Frontend/Mappers/StaticResourceMapper.cs index 98f282961..0799ef5d4 100644 --- a/src/Lidarr.Http/Frontend/Mappers/StaticResourceMapper.cs +++ b/src/Lidarr.Http/Frontend/Mappers/StaticResourceMapper.cs @@ -37,7 +37,7 @@ namespace Lidarr.Http.Frontend.Mappers } return resourceUrl.StartsWith("/content") || - (resourceUrl.EndsWith(".js") && !resourceUrl.EndsWith("initialize.js")) || + resourceUrl.EndsWith(".js") || resourceUrl.EndsWith(".map") || resourceUrl.EndsWith(".css") || (resourceUrl.EndsWith(".ico") && !resourceUrl.Equals("/favicon.ico")) || diff --git a/src/Lidarr.Http/Middleware/CacheableSpecification.cs b/src/Lidarr.Http/Middleware/CacheableSpecification.cs index e20f8a0bc..5d3fd10f1 100644 --- a/src/Lidarr.Http/Middleware/CacheableSpecification.cs +++ b/src/Lidarr.Http/Middleware/CacheableSpecification.cs @@ -46,7 +46,7 @@ namespace Lidarr.Http.Middleware return false; } - if (path.EndsWith("/initialize.js")) + if (path.EndsWith("/initialize.json")) { return false; }