@vcmap/plugin-cli 1.0.1 → 2.0.2

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.
package/src/serve.js CHANGED
@@ -1,202 +1,117 @@
1
- const path = require('path');
2
- const https = require('https');
3
- const http = require('http');
4
- const { URL } = require('url');
5
- const fs = require('fs');
6
- const WebpackDevServer = require('webpack-dev-server');
7
- const webpack = require('webpack');
8
- const { logger } = require('@vcsuite/cli-logger');
9
- const { getContext, resolveContext } = require('./context');
10
- const { getDevWebpackConfig } = require('./getWebpackConfig');
11
- const { getPluginName } = require('./packageJsonHelpers');
1
+ import fs from 'fs';
2
+ import { createServer } from 'vite';
3
+ import { createVuePlugin } from 'vite-plugin-vue2';
4
+ import express from 'express';
5
+ import { logger } from '@vcsuite/cli-logger';
6
+ import path from 'path';
7
+ import { VuetifyResolver } from 'unplugin-vue-components/dist/resolvers.js';
8
+ import Components from 'unplugin-vue-components/dist/vite.js';
9
+ import { getContext } from './context.js';
10
+ import {
11
+ addConfigRoute,
12
+ addIndexRoute,
13
+ addMapConfigRoute,
14
+ checkReservedDirectories,
15
+ createConfigJsonReloadPlugin,
16
+ printVcmapUiVersion,
17
+ resolveMapUi,
18
+ } from './hostingHelpers.js';
19
+ import { getPluginName } from './packageJsonHelpers.js';
12
20
 
13
- function httpGet(stringUrl, auth, handler) {
14
- const url = new URL(stringUrl);
15
- const options = {
16
- hostname: url.hostname,
17
- port: url.port,
18
- path: url.pathname,
19
- };
21
+ /**
22
+ * @typedef {HostingOptions} ServeOptions
23
+ * @property {string} [mapConfig] - an filename or URL to a map config
24
+ */
20
25
 
21
- if (auth) {
22
- options.headers = { Authorization: `Basic ${Buffer.from(auth).toString('base64')}` };
23
- }
24
- if (url.protocol === 'https:') {
25
- https.get(options, handler);
26
- } else {
27
- http.get(options, handler);
28
- }
29
- }
26
+ async function getProxy(protocol, port) {
27
+ const { default: getPluginProxies } = await import('@vcmap/ui/build/getPluginProxies.js');
28
+ const { determineHostIpFromInterfaces } = await import('@vcmap/ui/build/determineHost.js');
29
+ const { getInlinePlugins } = await import('@vcmap/ui/build/buildHelpers.js');
30
30
 
31
- function getIndexHtml(stringUrl, fileName, auth) {
32
- return new Promise((resolve, reject) => {
33
- httpGet(stringUrl, auth, (res) => {
34
- if (res.statusCode >= 400) {
35
- logger.error('got status code: ', res.statusCode);
36
- reject(new Error(`StatusCode ${res.statusCode}`));
37
- }
38
- const write = fs.createWriteStream(resolveContext(fileName));
39
- write.on('finish', resolve);
40
- write.on('error', (err) => {
41
- reject(err);
42
- });
43
- res.pipe(write);
44
- });
31
+ const target = `${protocol}://${determineHostIpFromInterfaces()}:${port}`;
32
+ const proxy = await getPluginProxies(target);
33
+ const mapUiPlugins = resolveMapUi('plugins');
34
+ const inlinePlugins = await getInlinePlugins();
35
+ inlinePlugins.forEach((inlinePlugin) => {
36
+ proxy[`^/plugins/${inlinePlugin}/.*`] = {
37
+ target,
38
+ rewrite: (route) => {
39
+ const rest = route.replace(new RegExp(`^/plugins/${inlinePlugin}/`), '');
40
+ const file = rest || 'index.js';
41
+ return path.posix.join(path.relative(getContext(), mapUiPlugins), inlinePlugin, file);
42
+ },
43
+ };
45
44
  });
46
- }
47
45
 
48
- let configJson = null;
46
+ const pluginRoutes = Object.keys(proxy);
47
+ const name = await getPluginName();
48
+ const hasThisPlugin = pluginRoutes.find(p => p.startsWith(`^/plugins/${name}`));
49
49
 
50
- /**
51
- * @param {string} fileName
52
- * @returns {Promise<Object>}
53
- */
54
- async function readConfigJson(fileName) {
55
- const configFileName = fileName || resolveContext('config.json');
56
- let config = {};
57
- if (fs.existsSync(configFileName)) {
58
- const content = await fs.promises.readFile(configFileName);
59
- config = JSON.parse(content.toString());
50
+ if (hasThisPlugin) {
51
+ delete proxy[hasThisPlugin];
60
52
  }
61
- // eslint-disable-next-line no-underscore-dangle
62
- delete config._esmodule;
63
- return config;
53
+ return proxy;
64
54
  }
65
55
 
66
- function getConfigJson(vcm, name, { auth, config: configFile }) {
67
- if (configJson) {
68
- return Promise.resolve(configJson);
56
+ /**
57
+ * @param {ServeOptions} options
58
+ * @returns {Promise<void>}
59
+ */
60
+ export default async function serve(options) {
61
+ if (!fs.existsSync(path.join(getContext(), 'node_modules', '@vcmap', 'ui'))) {
62
+ logger.error('Can only serve in dev mode, if the map ui is a dependency of the current context');
63
+ return;
69
64
  }
70
- const isWebVcm = /^https?:\/\//.test(vcm);
71
- return new Promise((resolve, reject) => {
72
- function handleStream(stream) {
73
- let data = '';
74
- stream.on('data', (chunk) => {
75
- data += chunk.toString();
76
- });
77
-
78
- stream.on('close', async () => {
79
- try {
80
- configJson = JSON.parse(data);
81
- configJson.plugins = configJson.plugins || [];
82
- const pluginConfig = await readConfigJson(configFile);
83
- // eslint-disable-next-line no-underscore-dangle
84
- pluginConfig.entry = '/_dist/plugin.js';
85
- pluginConfig.name = name;
86
- const idx = configJson.plugins.findIndex(p => p.name === name);
87
- if (idx > -1) {
88
- configJson.plugins.splice(idx, 1, pluginConfig);
89
- } else {
90
- configJson.plugins.push(pluginConfig);
91
- }
92
- resolve(configJson);
93
- } catch (e) {
94
- reject(e);
95
- }
96
- });
97
- }
98
- if (isWebVcm) {
99
- httpGet(`${vcm}config.json`, auth, (res) => {
100
- if (res.statusCode < 400) {
101
- handleStream(res);
102
- }
103
- });
104
- } else {
105
- handleStream(fs.createReadStream(path.join(vcm, 'config.json')));
106
- }
107
- });
108
- }
65
+ await printVcmapUiVersion();
66
+ checkReservedDirectories();
67
+ const app = express();
68
+ const port = options.port || 8008;
109
69
 
110
- async function serve(options) {
111
- logger.spin('Starting development server...');
112
- let { vcm, index } = options;
113
- const pluginName = options.pluginName || await getPluginName();
114
- const isWebVcm = /^https?:\/\//.test(vcm);
70
+ logger.info('Starting development server...');
71
+ const proxy = await getProxy(options.https ? 'https' : 'http', port);
115
72
 
116
- const proxy = {};
117
- const indexFilename = 'index.html'; // XXX maybe use some random filename when web to not clobber anything
118
- if (isWebVcm) {
119
- vcm = `${vcm.replace(/\/$/, '')}/`;
120
- await getIndexHtml(`${vcm}/${index}`, indexFilename, options.auth);
121
- ['/lib', '/css', '/fonts', '/images', '/img', '/templates', '/datasource-data', '/plugins']
122
- .concat(options.proxyRoute) // TODO allow for more complex proxy options, e.g add a target such as --proxyRoute myProxy=myTarget
123
- .forEach((p) => {
124
- proxy[p] = {
125
- target: vcm,
126
- changeOrigin: true,
127
- auth: options.auth,
128
- };
129
- });
130
- }
131
- const webpackConfig = await getDevWebpackConfig(options);
132
- const server = new WebpackDevServer(webpack(webpackConfig), {
133
- hot: true,
134
- hotOnly: true,
135
- open: false,
136
- injectClient: false,
137
- publicPath: '/_dist',
138
- logLevel: 'warn',
139
- clientLogLevel: 'silent',
140
- useLocalIp: true,
141
- historyApiFallback: {
142
- disableDotRule: true,
143
- rewrites: [
144
- { from: /./, to: '/index.html' },
73
+ const server = await createServer({
74
+ root: getContext(),
75
+ publicDir: false,
76
+ optimizeDeps: {
77
+ exclude: [
78
+ '@vcmap/ui',
79
+ '@vcmap/core',
80
+ 'ol',
81
+ '@vcsuite/ui-components',
82
+ 'proj4',
83
+ ],
84
+ include: [
85
+ 'fast-deep-equal',
86
+ 'rbush-knn',
87
+ 'pbf',
88
+ '@vcmap/cesium',
145
89
  ],
146
90
  },
147
- staticOptions: {
148
- fallthrough: false,
149
- },
150
- before(app) {
151
- app.use('/config.json', (req, res) => {
152
- getConfigJson(vcm, pluginName, options)
153
- .then((config) => {
154
- const stringConfig = JSON.stringify(config, null, 2);
155
- res.setHeader('Content-Type', 'application/json');
156
- res.write(stringConfig);
157
- res.end();
158
- });
159
- });
160
- },
161
- after(app) {
162
- app.use('/', (err, req, res, next) => {
163
- if (err.statusCode === 404 && isWebVcm) {
164
- httpGet(`${vcm}${req.url.replace(/^\//, '')}`, options.auth, (innerRes) => {
165
- if (innerRes.statusCode < 400) {
166
- innerRes.pipe(res);
167
- }
168
- });
169
- } else {
170
- next();
171
- }
172
- });
91
+ plugins: [
92
+ createVuePlugin(),
93
+ createConfigJsonReloadPlugin(),
94
+ Components({
95
+ resolvers: [
96
+ VuetifyResolver(),
97
+ ],
98
+ include: [],
99
+ exclude: [],
100
+ }),
101
+ ],
102
+ server: {
103
+ middlewareMode: 'html',
104
+ https: options.https,
105
+ proxy,
173
106
  },
174
- proxy,
175
- contentBase: isWebVcm ? getContext() : [options.vcm, getContext()],
176
- contentBasePublicPath: '/',
177
- index: 'index.html',
178
107
  });
179
108
 
180
- logger.stopSpinner();
181
- server.listen(options.port, '0.0.0.0', (err) => {
182
- if (err) {
183
- logger.error(err);
184
- } else {
185
- logger.success('Your application is running');
186
- }
187
- });
109
+ addMapConfigRoute(app, options.mapConfig, options.auth, options.config);
110
+ addIndexRoute(app, server);
111
+ await addConfigRoute(app, options.auth, options.config);
188
112
 
189
- ['SIGINT', 'SIGTERM'].forEach((signal) => {
190
- process.on(signal, () => {
191
- server.close(() => {
192
- if (isWebVcm) {
193
- fs.unlinkSync(resolveContext(index));
194
- }
195
- process.exit(0);
196
- });
197
- });
198
- });
199
- }
113
+ app.use(server.middlewares);
200
114
 
201
-
202
- module.exports = serve;
115
+ await app.listen(port);
116
+ logger.info(`Server running on port ${port}`);
117
+ }
@@ -0,0 +1,9 @@
1
+ import { logger } from '@vcsuite/cli-logger';
2
+ import { executeUiNpm } from './hostingHelpers.js';
3
+
4
+ export default async function setupMapUi() {
5
+ logger.spin('installing dev plugins in @vcmap/ui');
6
+ await executeUiNpm('install-plugins');
7
+ logger.stopSpinner();
8
+ logger.info('dev plugins installed');
9
+ }
@@ -1,290 +0,0 @@
1
- const path = require('path');
2
- const webpack = require('webpack');
3
- const VueLoaderPlugin = require('vue-loader/lib/plugin');
4
- const TerserPlugin = require('terser-webpack-plugin');
5
- const autoprefixer = require('autoprefixer');
6
- const { getPluginEntry } = require('./packageJsonHelpers');
7
- const { resolveContext, getContext } = require('./context');
8
-
9
- /**
10
- * @enum {string}
11
- */
12
- const buildMode = {
13
- DEVELOPMENT: 'development',
14
- PRODUCTION: 'production',
15
- TESTING: 'testing',
16
- };
17
-
18
- /**
19
- * @typedef {GetWebpackOptions} DevOptions
20
- * @property {string|undefined} vcm - the vcm directory
21
- * @property {number} port - the port number of the dev server // XXX take these options apart
22
- */
23
-
24
- /**
25
- * @typedef {GetWebpackOptions} ProdOptions
26
- * @property {string} pluginName - the name of the plugin being built
27
- * @property {boolean|undefined} modern - build for modern browsers
28
- */
29
-
30
- /**
31
- * @typedef {Object} GetWebpackOptions
32
- * @property {string|Object|undefined} entry - an alternative entry point, defaults to 'src/index'
33
- * @property {string|undefined} mode - 'development', 'production' or 'test'.
34
- * @property {boolean|undefined} condenseWhitespace - pass whitespace: 'condense' to vue loader
35
- */
36
-
37
- /**
38
- * @param {GetWebpackOptions} options
39
- * @returns {webpack.Configuration}
40
- */
41
- function getBaseConfig(options) {
42
- return {
43
- experiments: {
44
- outputModule: true,
45
- },
46
- externals: {
47
- '@vcmap/core': 'import @vcmap-core.js',
48
- },
49
- entry: options.entry,
50
- context: getContext(),
51
- resolve: {
52
- extensions: ['.js', '.vue', '.json'],
53
- alias: {
54
- '@': resolveContext('src'),
55
- },
56
- modules: [
57
- path.join(__dirname, '..', 'node_modules'),
58
- 'node_modules',
59
- resolveContext('node_modules'),
60
- ],
61
- },
62
- resolveLoader: {
63
- modules: [
64
- path.join(__dirname, '..', 'node_modules'),
65
- 'node_modules',
66
- resolveContext('node_modules'),
67
- ],
68
- },
69
- module: {
70
- rules: [
71
- {
72
- test: /\.vue$/,
73
- loader: 'vue-loader',
74
- options: {
75
- compilerOptions: {
76
- whitespace: options.condenseWhitespace ? 'condense' : 'preserve',
77
- },
78
- },
79
- },
80
- {
81
- test: /\.js$/,
82
- loader: 'babel-loader',
83
- include: [resolveContext('src')],
84
- options: {
85
- presets: [
86
- require.resolve('@vue/babel-preset-app'),
87
- ],
88
- },
89
- },
90
- {
91
- test: /\.(png|jpe?g|gif)(\?.*)?$/,
92
- use: [
93
- {
94
- loader: 'url-loader',
95
- options: {
96
- limit: 10000,
97
- name: 'img/[name].[hash:8].[ext]',
98
- },
99
- },
100
- ],
101
- },
102
- {
103
- test: /\.(svg)(\?.*)?$/,
104
- use: [
105
- {
106
- loader: 'file-loader',
107
- options: {
108
- name: 'img/[name].[hash:8].[ext]',
109
- },
110
- },
111
- ],
112
- },
113
- {
114
- test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
115
- use: [
116
- {
117
- loader: 'url-loader',
118
- options: {
119
- limit: 10000,
120
- name: 'media/[name].[hash:8].[ext]',
121
- },
122
- },
123
- ],
124
- },
125
- {
126
- test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
127
- use: [
128
- {
129
- loader: 'url-loader',
130
- options: {
131
- limit: 10000,
132
- name: 'fonts/[name].[hash:8].[ext]',
133
- },
134
- },
135
- ],
136
- },
137
- {
138
- test: /\.css$/,
139
- use: [
140
- {
141
- loader: 'vue-style-loader',
142
- options: {
143
- base: 1020,
144
- shadowMode: false,
145
- },
146
- },
147
- {
148
- loader: 'css-loader',
149
- options: {
150
- sourceMap: false,
151
- importLoaders: 2,
152
- },
153
- },
154
- {
155
- loader: 'postcss-loader',
156
- options: {
157
- sourceMap: false,
158
- plugins: [
159
- autoprefixer,
160
- ],
161
- },
162
- },
163
- ],
164
- },
165
- {
166
- test: /\.p(ost)?css$/,
167
- use: [
168
- {
169
- loader: 'vue-style-loader',
170
- options: {
171
- base: 1020,
172
- shadowMode: false,
173
- },
174
- },
175
- {
176
- loader: 'css-loader',
177
- options: {
178
- sourceMap: false,
179
- importLoaders: 2,
180
- },
181
- },
182
- {
183
- loader: 'postcss-loader',
184
- options: {
185
- sourceMap: false,
186
- plugins: [
187
- autoprefixer,
188
- ],
189
- },
190
- },
191
- ],
192
- },
193
- ],
194
- },
195
- plugins: [
196
- new VueLoaderPlugin(),
197
- new webpack.DefinePlugin({
198
- 'process.env': { NODE_ENV: `"${options.mode}"`, BASE_URL: '""' },
199
- }),
200
- ],
201
- };
202
- }
203
-
204
- /**
205
- * @param {ProdOptions} options
206
- * @returns {Promise<webpack.Configuration>}
207
- */
208
- async function getProdWebpackConfig(options) {
209
- options.entry = options.entry || { plugin: await getPluginEntry() || './src/index' };
210
- options.mode = options.mode || buildMode.PRODUCTION;
211
- process.env.VUE_CLI_MODERN_BUILD = true;
212
-
213
- if (typeof options.entry === 'string') {
214
- options.entry = { plugin: options.entry };
215
- }
216
-
217
- const config = getBaseConfig(options);
218
- config.output = {
219
- path: resolveContext('dist'),
220
- filename: `${options.pluginName}.js`,
221
- library: {
222
- type: 'module',
223
- },
224
- publicPath: './',
225
- };
226
-
227
- config.mode = options.mode;
228
- if (options.mode !== 'development') {
229
- config.devtool = false;
230
- config.optimization = {
231
- minimize: true,
232
- minimizer: [
233
- new TerserPlugin({
234
- extractComments: false,
235
- }),
236
- ],
237
- };
238
- }
239
-
240
- return config;
241
- }
242
-
243
- /**
244
- * @param {DevOptions} options
245
- * @returns {Promise<webpack.Configuration>}
246
- */
247
- async function getDevWebpackConfig(options) {
248
- options.entry = options.entry || {
249
- plugin: [
250
- `webpack-dev-server/client?http://0.0.0.0:${options.port}/`,
251
- 'webpack/hot/only-dev-server',
252
- await getPluginEntry() || './src/index.js',
253
- ],
254
- };
255
- options.mode = options.mode || buildMode.DEVELOPMENT;
256
- if (typeof options.entry === 'string') {
257
- options.entry = {
258
- plugin: [
259
- `webpack-dev-server/client?http://0.0.0.0:${options.port}/`,
260
- 'webpack/hot/only-dev-server',
261
- options.entry,
262
- ],
263
- };
264
- }
265
- const config = getBaseConfig(options);
266
-
267
- config.output = {
268
- globalObject: '(typeof self !== \'undefined\' ? self : this)',
269
- path: resolveContext('_dist'),
270
- library: {
271
- type: 'module',
272
- },
273
- filename: '[name].js',
274
- publicPath: '/_dist',
275
- };
276
-
277
- config.plugins.push(
278
- new webpack.HotModuleReplacementPlugin(),
279
- // new webpack.NoEmitOnErrorsPlugin(),
280
- );
281
- config.mode = options.mode;
282
- config.devtool = 'eval-cheap-module-source-map';
283
- return config;
284
- }
285
-
286
- module.exports = {
287
- getBaseConfig,
288
- getProdWebpackConfig,
289
- getDevWebpackConfig,
290
- };
@@ -1,29 +0,0 @@
1
- const { getBaseConfig } = require('./getWebpackConfig');
2
- const { getPluginEntry } = require('./packageJsonHelpers');
3
-
4
- /**
5
- * Use this file to point your ID eslint settings to a webpack resolver. Do to a bug in `eslint-import-resolver-webpack`
6
- * you must provide the ENTRY env for use with eslint, see example.
7
- * @example
8
- * 'import/resolver': {
9
- * webpack: {
10
- * config: 'node_modules/vcmplugin-cli/src/webpack.config.js'
11
- * env: {
12
- * ENTRY: './index.js'
13
- * }
14
- * }
15
- * }
16
- * @param {Object=} env
17
- * @returns {webpack.Configuration|Promise<webpack.Configuration>}
18
- */
19
- function getConfig(env) {
20
- if (env && env.ENTRY) {
21
- return getBaseConfig({ entry: env.ENTRY, mode: 'development' });
22
- }
23
- return getPluginEntry()
24
- .then((entry) => {
25
- return getBaseConfig({ entry, mode: 'development' });
26
- });
27
- }
28
-
29
- module.exports = getConfig;