Switch to esm fully

This commit is contained in:
MattIPv4 2022-02-04 18:02:21 +00:00
parent 217337745a
commit 9cd2057cd8
14 changed files with 165 additions and 181 deletions

View File

@ -8,6 +8,7 @@
"node": "14.17.5"
},
"main": "src/nginxconfig/mount.js",
"type": "module",
"scripts": {
"build": "npm run build:clean && npm run build:template && npm run build:prism && npm run build:static && npm run build:tool",
"build:clean": "do-vue clean",
@ -24,7 +25,7 @@
"test:eslint": "eslint 'src/**/*.{js,vue}'",
"test:eslint:fix": "npm run test:eslint -- --fix",
"test:sass-lint": "sass-lint 'src/**/*.scss' --verbose --no-exit --config .sasslintrc",
"test:i18n-packs": "node -r esm src/nginxconfig/i18n/verify.js"
"test:i18n-packs": "node --es-module-specifier-resolution=node src/nginxconfig/i18n/verify.js"
},
"jest": {
"testRegex": "/test/.*.js?$"

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -24,9 +24,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
const fs = require('fs').promises;
const path = require('path');
const fetch = require('node-fetch');
import { promises as fs } from 'fs';
import { URL } from 'url';
import fetch from 'node-fetch';
const main = async () => {
const resp = await fetch('https://assets.digitalocean.com/prism/prism.css');
@ -35,8 +35,11 @@ const main = async () => {
// Fix $676767 -> #676767
const fixed = text.replace(/:\s*\$((?:[0-9a-fA-F]{3}){1,2});/g, ':#$1;');
const buildDir = path.join(__dirname, '..', '..', '..', 'build');
await fs.writeFile(path.join(buildDir, 'prism.css'), fixed);
const buildDir = '../../../build';
await fs.writeFile(new URL(`${buildDir}/prism.css`, import.meta.url), fixed);
};
main().then(() => {});
main().then(() => {}).catch(err => {
console.error(err);
process.exit(1);
});

View File

@ -24,13 +24,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
const path = require('path');
const fs = require('fs');
import fs from 'fs';
import { URL } from 'url';
// Fetch the posthtml template and convert it to an ejs template
const main = () => {
const buildDir = path.join(__dirname, '..', '..', '..', 'build');
let template = fs.readFileSync(path.join(buildDir, 'base.html'), 'utf8');
const buildDir = '../../../build';
let template = fs.readFileSync(new URL(`${buildDir}/base.html`, import.meta.url), 'utf8');
// Inject our title now
template = template.replace('<block name="title"><title>DigitalOcean</title></block>', '<title>NGINXConfig | DigitalOcean</title>');
@ -38,7 +38,7 @@ const main = () => {
// Inject our app mounting point
template = template.replace('<block name="content"></block>', '<div id="app"></div>');
fs.writeFileSync(path.join(buildDir, 'index.html'), template);
fs.writeFileSync(new URL(`${buildDir}/index.html`, import.meta.url), template);
};
main();

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
const yaml = require('json-to-pretty-yaml');
import yaml from 'json-to-pretty-yaml';
export default yamlConf => {
return yaml.stringify(yamlConf);

View File

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -26,9 +26,7 @@ THE SOFTWARE.
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import { defaultPack, defaultPackData } from '../util/language_pack_default';
import { toSep } from '../util/language_pack_name';
import { languagePackContext, availablePacks } from '../util/language_pack_context';
import { defaultPack, defaultPackData, toSep, availablePacks } from '../util/language_packs';
Vue.use(VueI18n);
@ -37,32 +35,58 @@ const i18nPacks = {};
i18nPacks[defaultPack] = defaultPackData;
const loadedI18nPacks = [defaultPack];
// Load in languages data from other packs
// Use webpack magic to only build chunks for lang/languages.js
const languagesContext = require.context('.', true, /^\.\/[^/]+\/languages\.js$/, 'sync');
for (const availablePack of availablePacks) {
if (availablePack === defaultPack) continue;
i18nPacks[availablePack] = { languages: languagesContext(`./${toSep(availablePack, '-')}/languages.js`).default };
}
// Cache the i18n instance
let i18n = null;
export const i18n = new VueI18n({
locale: defaultPack,
fallbackLocale: defaultPack,
messages: i18nPacks,
});
export const getI18n = async () => {
// Use cached if available
if (i18n) return i18n;
const loadLanguagePack = pack => {
// Load in languages data from other packs
// Use webpack magic to only build chunks for lang/languages.js
// These are eagerly loaded by Webpack, so don't generate extra chunks, and return an already resolved Promise
for (const availablePack of availablePacks) {
if (availablePack === defaultPack) continue;
if (i18nPacks[availablePack]) continue;
const { default: languageData } = await import(
/* webpackInclude: /i18n\/[^/]+\/languages\.js$/ */
/* webpackMode: "eager" */
`./${toSep(availablePack, '-')}/languages.js`
);
i18nPacks[availablePack] = { languages: languageData };
}
// Store and return the i18n instance with the loaded packs
i18n = new VueI18n({
locale: defaultPack,
fallbackLocale: defaultPack,
messages: i18nPacks,
});
return i18n;
};
const loadLanguagePack = async pack => {
// If same language, do nothing
if (i18n.locale === pack) return;
// If language already loaded, do nothing
if (loadedI18nPacks.includes(pack)) return;
// Load the pack with webpack magic
return languagePackContext(`./${toSep(pack, '-')}/index.js`).then(packData => i18nPacks[pack] = packData.default);
// Load in the full pack
// Use webpack magic to only build chunks for lang/index.js
const { default: packData } = await import(
/* webpackInclude: /i18n\/[^/]+\/index\.js$/ */
/* webpackMode: "lazy" */
`./${toSep(pack, '-')}/index.js`
);
i18nPacks[pack] = packData;
};
export const setLanguagePack = async pack => {
// If i18n not loaded, do nothing
if (!i18n) return;
// Load the pack if not already loaded, and set it as active
await loadLanguagePack(pack);
i18n.locale = pack;
};

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -26,19 +26,11 @@ THE SOFTWARE.
import { readdirSync, readFileSync } from 'fs';
import { join, sep } from 'path';
import { URL } from 'url';
import chalk from 'chalk';
import { defaultPack } from '../util/language_pack_default';
import { toSep, fromSep } from '../util/language_pack_name';
import { defaultPack, toSep, fromSep } from '../util/language_packs';
import snakeToCamel from '../util/snake_to_camel';
// Load all the packs in
const packs = {};
const packDirectories = readdirSync(__dirname, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
for (const packDirectory of packDirectories)
packs[fromSep(packDirectory, '-')] = require(`./${packDirectory}/index.js`).default;
// Recursively get all keys in a i18n pack object fragment
const explore = packFragment => {
const foundKeys = new Set();
@ -61,7 +53,7 @@ const explore = packFragment => {
const files = directory => {
const foundFiles = new Set();
for (const dirent of readdirSync(join(__dirname, directory), { withFileTypes: true })) {
for (const dirent of readdirSync(new URL(`./${directory}`, import.meta.url), { withFileTypes: true })) {
const base = join(directory, dirent.name);
// If this is a file, store it
@ -83,7 +75,7 @@ const files = directory => {
// Get all the todo items in a file
const todos = file => {
const content = readFileSync(join(__dirname, file), 'utf8');
const content = readFileSync(new URL(`./${file}`, import.meta.url), 'utf8');
const lines = content.split('\n');
const items = [];
@ -105,63 +97,78 @@ const fileToObject = file => file
// Replace sep with period and use camelCase
.split(sep).map(dir => snakeToCamel(dir)).join('.');
// Get all the keys for the default "source" language pack
const defaultKeys = explore(packs[defaultPack]);
const main = async () => {
// Load all the packs in
const packs = {};
const packDirectories = readdirSync(new URL('./', import.meta.url), { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
for (const packDirectory of packDirectories)
packs[fromSep(packDirectory, '-')] = await import(`./${packDirectory}/index.js`).then(pack => pack.default);
// Track if we need to exit with an error
let hadError = false;
// Get all the keys for the default "source" language pack
const defaultKeys = explore(packs[defaultPack]);
// Work through all the packs and compare to default
for (const [pack, packData] of Object.entries(packs)) {
console.log(chalk.underline(`Language pack \`${pack}\``));
// Track if we need to exit with an error
let hadError = false;
// Get the base data
const packKeys = explore(packData);
const packFiles = files(toSep(pack, '-'));
console.log(` Found ${packKeys.size.toLocaleString()} keys, ${packFiles.size.toLocaleString()} files`);
// Work through all the packs and compare to default
for (const [pack, packData] of Object.entries(packs)) {
console.log(chalk.underline(`Language pack \`${pack}\``));
// Track all our errors and warnings
const errors = [], warnings = [];
// Get the base data
const packKeys = explore(packData);
const packFiles = files(toSep(pack, '-'));
console.log(` Found ${packKeys.size.toLocaleString()} keys, ${packFiles.size.toLocaleString()} files`);
// Get all the keys and the set differences
const missingKeys = [...defaultKeys].filter(x => !packKeys.has(x));
const extraKeys = [...packKeys].filter(x => !defaultKeys.has(x));
// Track all our errors and warnings
const errors = [], warnings = [];
// Missing keys and extra keys are errors
missingKeys.forEach(key => errors.push(`Missing key \`${key}\``));
extraKeys.forEach(key => errors.push(`Unexpected key \`${key}\``));
// Get all the keys and the set differences
const missingKeys = [...defaultKeys].filter(x => !packKeys.has(x));
const extraKeys = [...packKeys].filter(x => !defaultKeys.has(x));
// Get all the files in the pack directory
const packKeyFiles = new Set([...packFiles].filter(file => file.split(sep).slice(-1)[0] !== 'index.js'));
// Missing keys and extra keys are errors
missingKeys.forEach(key => errors.push(`Missing key \`${key}\``));
extraKeys.forEach(key => errors.push(`Unexpected key \`${key}\``));
// Get the objects from the pack keys
const packKeyObjects = new Set([...packKeys]
.map(key => key.split('.').slice(0, -1).join('.')));
// Get all the files in the pack directory
const packKeyFiles = new Set([...packFiles].filter(file => file.split(sep).slice(-1)[0] !== 'index.js'));
// Warn for any files that aren't used as pack objects
[...packKeyFiles].filter(file => !packKeyObjects.has(fileToObject(file)))
.forEach(file => warnings.push(`Unused file \`${file}\``));
// Get the objects from the pack keys
const packKeyObjects = new Set([...packKeys]
.map(key => key.split('.').slice(0, -1).join('.')));
// Locate any todos in each file as a warning
for (const file of packFiles)
todos(file).forEach(todo => warnings.push(`TODO in \`${file}\` on line ${todo[0]}`));
// Warn for any files that aren't used as pack objects
[...packKeyFiles].filter(file => !packKeyObjects.has(fileToObject(file)))
.forEach(file => warnings.push(`Unused file \`${file}\``));
// Output the pack results
if (warnings.length)
for (const warning of warnings)
console.log(` ${chalk.yellow('warning')} ${warning}`);
if (errors.length)
for (const error of errors)
console.log(` ${chalk.red('error')} ${error}`);
if (!errors.length && !warnings.length)
console.log(` ${chalk.green('No issues')}`);
// Locate any todos in each file as a warning
for (const file of packFiles)
todos(file).forEach(todo => warnings.push(`TODO in \`${file}\` on line ${todo[0]}`));
// If we had errors, script should exit 1
if (errors.length) hadError = true;
// Output the pack results
if (warnings.length)
for (const warning of warnings)
console.log(` ${chalk.yellow('warning')} ${warning}`);
if (errors.length)
for (const error of errors)
console.log(` ${chalk.red('error')} ${error}`);
if (!errors.length && !warnings.length)
console.log(` ${chalk.green('No issues')}`);
// Linebreak before next pack or exit
console.log(chalk.reset());
}
// If we had errors, script should exit 1
if (errors.length) hadError = true;
// Exit 1 if we had errors
if (hadError) process.exit(1);
// Linebreak before next pack or exit
console.log(chalk.reset());
}
// Exit 1 if we had errors
if (hadError) process.exit(1);
};
main().then(() => {}).catch(err => {
console.error(err);
process.exit(1);
});

View File

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -28,11 +28,14 @@ THE SOFTWARE.
import './scss/style.scss';
import Vue from 'vue';
import './util/prism_bundle';
import { i18n } from './i18n/setup';
import { getI18n } from './i18n/setup';
import App from './templates/app';
// Run the app
new Vue({
i18n,
render: h => h(App),
}).$mount('#app');
// Load the i18n languages and run the app
getI18n().then(i18n => {
new Vue({
i18n,
render: h => h(App),
}).$mount('#app');
});

View File

@ -133,8 +133,7 @@ THE SOFTWARE.
import isObject from '../util/is_object';
import analytics from '../util/analytics';
import browserLanguage from '../util/browser_language';
import { defaultPack } from '../util/language_pack_default';
import { availablePacks } from '../util/language_pack_context';
import { defaultPack, availablePacks } from '../util/language_packs';
import { setLanguagePack } from '../i18n/setup';
import generators from '../generators';

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { fromSep } from './language_pack_name';
import { fromSep } from './language_packs';
export default availablePacks => {
if (typeof window === 'object' && typeof window.navigator === 'object') {

View File

@ -1,37 +0,0 @@
/*
Copyright 2020 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
https://github.com/digitalocean/nginxconfig.io/blob/master/LICENSE or https://mit-license.org/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { fromSep } from './language_pack_name';
// Use webpack magic to only build chunks for lang/index.js, not subdirectories (e.g. lang/templates/index.js)
export const languagePackContext = require.context('../i18n', true, /^\.\/[^/]+\/index\.js$/, 'lazy');
// Webpack magic to get all the packs that are available
export const availablePacks = Object.freeze(languagePackContext
.keys()
.map(pack => pack.match(/^\.\/([^/]+)\/index\.js$/))
.filter(pack => pack !== null)
.map(pack => fromSep(pack[1], '-')));

View File

@ -1,30 +0,0 @@
/*
Copyright 2020 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
https://github.com/digitalocean/nginxconfig.io/blob/master/LICENSE or https://mit-license.org/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
export const defaultPack = 'en';
export { default as defaultPackData } from '../i18n/en';

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -24,6 +24,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
export const defaultPack = 'en';
export { default as defaultPackData } from '../i18n/en';
export const toSep = (pack, sep) => pack
.match(/^([a-z]+)([A-Z]*)$/)
.slice(1)
@ -32,3 +36,13 @@ export const toSep = (pack, sep) => pack
.join(sep);
export const fromSep = (pack, sep) => pack.split(sep, 2)[0].toLowerCase() + (pack.split(sep, 2)[1] || '').toUpperCase();
// Use webpack magic to get all the language packs we've bundled
// If not in a webpack context, return no packs
/* global __webpack_modules__ */
export const availablePacks = typeof __webpack_modules__ === 'undefined'
? []
: Object.keys(__webpack_modules__)
.map(pack => pack.match(/i18n\/([^/]+)\/languages\.js$/))
.filter(pack => pack !== null)
.map(pack => fromSep(pack[1], '-'));

View File

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -24,12 +24,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
const path = require('path');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
const WebpackRequireFrom = require('webpack-require-from');
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import DuplicatePackageCheckerPlugin from 'duplicate-package-checker-webpack-plugin';
import WebpackRequireFrom from 'webpack-require-from';
import { URL, fileURLToPath } from 'url';
module.exports = {
export default {
publicPath: './',
outputDir: 'dist',
filenameHashing: false, // Don't hash the output, so we can embed on the DigitalOcean Community
@ -43,10 +43,10 @@ module.exports = {
// Fix dynamic imports from CDN (inject as first entry point before any imports can happen)
{ apply: compiler => {
compiler.options.entry.app.import.unshift(
path.join(__dirname, 'src', 'nginxconfig', 'build', 'webpack-dynamic-import.js'),
fileURLToPath(new URL('src/nginxconfig/build/webpack-dynamic-import.js', import.meta.url)),
);
} },
new WebpackRequireFrom({ methodName: '__webpackDynamicImportURL' }),
new WebpackRequireFrom({ methodName: '__webpackDynamicImportURL', suppressErrors: true }),
// Analyze the bundle
new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false }),
new DuplicatePackageCheckerPlugin(),
@ -68,7 +68,7 @@ module.exports = {
// Use a custom HTML template
config.plugin('html').tap(options => {
options[0].template = path.join(__dirname, 'build', 'index.html');
options[0].template = fileURLToPath(new URL('build/index.html', import.meta.url));
return options;
});
},