diff --git a/Dockerfile b/Dockerfile index d9be89d0b..8c44585e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,11 +31,11 @@ RUN apt-get update \ WORKDIR /salix COPY print/package.json print/package-lock.json print/ -RUN npm --prefix ./print install --omit=dev ./print +RUN npm --prefix ./print install ./print COPY package.json package-lock.json ./ COPY loopback/package.json loopback/ -RUN npm install --omit=dev +RUN npm install COPY loopback loopback COPY storage storage diff --git a/docker-compose.local.yml b/docker-compose.local.yml index fec7ac2f9..72e0ad504 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -9,13 +9,11 @@ services: image: front build: context: . - dockerfile: front/Dockerfile - target: development + dockerfile: front/Dockerfile.local ports: - - 5000:80 - links: - - back - + - 5000:5000 + depends_on: + - back back: image: salix-back build: @@ -27,9 +25,6 @@ services: - NODE_ENV depends_on: - db - local: - image: - networks: salix-stack-network: driver: host diff --git a/front/Dockerfile.local b/front/Dockerfile.local new file mode 100644 index 000000000..d54d53436 --- /dev/null +++ b/front/Dockerfile.local @@ -0,0 +1,33 @@ +FROM salix-back +EXPOSE 5000 + +# ENV TZ Europe/Madrid + +# WORKDIR /front + +# COPY /front/gulpfile.js ./ +# COPY /front/webpack.config.js ./ +# COPY modules modules + +# RUN npm install \ +# require-yaml \ +# del@2.2.2 \ +# plugin-error \ +# minimist \ +# gulp@4.0.2 gulp-wrap gulp-concat gulp-merge-json gulp-file gulp-yaml\ +# webpack@5.83.1 webpack-merge@4.2.2 webpack-dev-server@3.11.0 html-webpack-plugin@5.5.1 \ +# fancy-log \ +# merge-stream@1.0.1 \ +# fs-extra +WORKDIR /salix + +COPY /dist dist +COPY /front front +COPY /front/gulpfile.js ./ +COPY /front/webpack.config.js ./ + +RUN cd front && npm install +RUN npx gulp build +# RUN npx gulp front + +CMD ["npx", "gulp", "front"] diff --git a/front/gulpfile.js b/front/gulpfile.js new file mode 100644 index 000000000..fa1ed0bf5 --- /dev/null +++ b/front/gulpfile.js @@ -0,0 +1,178 @@ +require('require-yaml'); +const gulp = require('gulp'); +const PluginError = require('plugin-error'); +const argv = require('minimist')(process.argv.slice(2)); +const log = require('fancy-log'); + +// Configuration + +if (argv.NODE_ENV) + process.env.NODE_ENV = argv.NODE_ENV; + +let langs = ['es', 'en']; +let srcDir = './front'; +let modulesDir = './modules'; +let buildDir = 'dist'; + +// Development + +const localesRoutes = gulp.parallel(locales, routes); +localesRoutes.description = `Builds locales and routes`; + +const front = gulp.series(clean, gulp.parallel(localesRoutes, watch, webpackDevServer)); +front.description = `Starts frontend service`; + +function clean() { + const del = require('del'); + const files = [ + `${buildDir}/*` + ]; + return del(files, {force: true}); +} +clean.description = `Cleans all files generated by the 'build' task`; + +// Webpack + +function webpack(done) { + const webpackCompile = require('webpack'); + const merge = require('webpack-merge'); + + let wpConfig = require('./webpack.config.js'); + wpConfig = merge(wpConfig, {}); + + let compiler = webpackCompile(wpConfig); + + compiler.run(function(err, stats) { + if (err) throw new PluginError('webpack', err); + log('[webpack]', stats.toString(wpConfig.stats)); + done(); + }); +} +webpack.description = `Transpiles application into files`; + +function webpackDevServer(done) { + const webpack = require('webpack'); + const merge = require('webpack-merge'); + const WebpackDevServer = require('webpack-dev-server'); + + let wpConfig = require('./webpack.config.js'); + wpConfig = merge(wpConfig, {}); + + let devServer = wpConfig.devServer; + + for (let entryName in wpConfig.entry) { + let entry = wpConfig.entry[entryName]; + if (!Array.isArray(entry)) + entry = [entry]; + + let wdsAssets = [ + `webpack-dev-server/client?http://localhost:${devServer.port}/`, + `webpack/hot/dev-server` + ]; + wpConfig.entry[entryName] = wdsAssets.concat(entry); + } + + let compiler = webpack(wpConfig); + new WebpackDevServer(compiler, wpConfig.devServer) + .listen(devServer.port, devServer.host, function(err) { + if (err) throw new PluginError('webpack-dev-server', err); + // XXX: Keep the server alive or continue? + done(); + }); +} +webpackDevServer.description = `Transpiles application into memory`; + +// Locale + +let localeFiles = [ + `${srcDir}/**/locale/*.yml`, + `${modulesDir}/*/front/**/locale/*.yml` +]; + +/** + * Mixes all locale files into one JSON file per module and language. It looks + * recursively in all project directories for locale folders with per language + * yaml translation files. + * + * @return {Stream} The merged gulp streams + */ +function locales() { + const mergeJson = require('gulp-merge-json'); + const gulpFile = require('gulp-file'); + const yaml = require('gulp-yaml'); + const merge = require('merge-stream'); + const fs = require('fs-extra'); + + let streams = []; + let localePaths = []; + + let modules = fs.readdirSync(modulesDir); + for (let mod of modules) + localePaths[mod] = `${modulesDir}/${mod}`; + + let baseMods = ['core', 'salix']; + for (let mod of baseMods) + localePaths[mod] = `${srcDir}/${mod}`; + + for (let mod in localePaths) { + let path = localePaths[mod]; + for (let lang of langs) { + let localeFiles = `${path}/**/locale/${lang}.yml`; + streams.push(gulp.src(localeFiles) + .pipe(yaml()) + .pipe(mergeJson({fileName: `${lang}.json`})) + .pipe(gulp.dest(`${buildDir}/locale/${mod}`))); + } + } + + for (let mod in localePaths) { + for (let lang of langs) { + let file = `${buildDir}/locale/${mod}/${lang}.json`; + if (fs.existsSync(file)) continue; + streams.push(gulpFile('en.json', '{}', {src: true}) + .pipe(gulp.dest(`${buildDir}/locale/${mod}`))); + } + } + + return merge(streams); +} +locales.description = `Generates client locale files`; + +// Routes + +let routeFiles = `${modulesDir}/*/front/routes.json`; + +function routes() { + const concat = require('gulp-concat'); + const wrap = require('gulp-wrap'); + + return gulp.src(routeFiles) + .pipe(concat('routes.js', {newLine: ','})) + .pipe(wrap('var routes = [<%=contents%>\n];')) + .pipe(gulp.dest(buildDir)); +} +routes.description = 'Merges all module routes file into one file'; + +// Watch + +function watch(done) { + gulp.watch(routeFiles, gulp.series(routes)); + gulp.watch(localeFiles, gulp.series(locales)); + done(); +} +watch.description = `Watches for changes in routes and locale files`; + +const build = gulp.series(clean, gulp.parallel(localesRoutes, webpack)); +build.description = `Generates binaries and configuration files`; + +module.exports = { + front, + build, + clean, + webpack, + webpackDevServer, + routes, + locales, + localesRoutes, + watch +}; diff --git a/front/webpack.config.js b/front/webpack.config.js new file mode 100644 index 000000000..84155be2f --- /dev/null +++ b/front/webpack.config.js @@ -0,0 +1,160 @@ +require('require-yaml'); +const webpack = require('webpack'); +const path = require('path'); +const merge = require('webpack-merge'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +let env = process.env.NODE_ENV || 'development'; +let mode = env == 'development' ? env : 'production'; + +let baseConfig = { + entry: {salix: 'salix'}, + mode, + output: { + path: path.join(__dirname, 'dist'), + publicPath: '/' + }, + module: { + rules: [ + { + test: /\.js$/, + loader: 'babel-loader', + exclude: /node_modules/, + options: { + presets: ['@babel/preset-env'], + plugins: ['@babel/plugin-syntax-dynamic-import'] + } + }, { + test: /\.yml$/, + use: ['json-loader!yaml-loader'] + }, { + test: /\.html$/, + loader: 'html-loader', + options: { + attrs: [ + 'img:src', + 'link:href' + ] + } + }, { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, { + test: /\.scss$/, + use: [ + 'style-loader', 'css-loader', { + loader: 'sass-loader', + options: { + // XXX: Don't work in Firefox + // https://github.com/webpack-contrib/style-loader/issues/303 + // sourceMap: true, + sassOptions: { + includePaths: [ + path.resolve(__dirname, 'front/core/styles/') + ] + } + } + } + ] + }, { + test: /\.(woff(2)?|ttf|eot|svg|png)(\?v=\d+\.\d+\.\d+)?$/, + type: 'asset/resource', + }, { + test: /manifest\.json$/, + type: 'javascript/auto', + loader: 'file-loader', + options: { + esModule: false, + } + } + ] + }, + optimization: { + runtimeChunk: true, + splitChunks: { + chunks: 'all', + } + }, + resolve: { + modules: [ + `front`, + `modules`, + `front/node_modules`, + `node_modules` + ], + alias: { + 'vn-loopback': `${__dirname}/loopback` + } + }, + watchOptions: { + ignored: [ + 'node_modules', + './modules/*/back/**' + ] + }, + plugins: [ + new HtmlWebpackPlugin({ + template: 'front/salix/index.ejs', + favicon: 'front/salix/favicon.ico', + filename: 'index.html', + chunks: ['salix'] + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(env) + }) + ], + devtool: 'source-map', + stats: { + assets: false, + modules: false, + children: false, + entrypoints: false, + colors: true, + errorDetails: true + } +}; + +let prodConfig = { + output: { + filename: '[name].[chunkhash].js', + chunkFilename: '[id].[chunkhash].js' + }, + plugins: [ + new webpack.ids.HashedModuleIdsPlugin() + ], + performance: { + maxEntrypointSize: 2000000, + maxAssetSize: 2000000 + } +}; + +let devConfig = { + output: { + filename: '[name].js', + chunkFilename: '[id].js' + }, + plugins: [ + new webpack.HotModuleReplacementPlugin() + ], + devServer: { + host: '0.0.0.0', + port: 5000, + publicPath: '/', + contentBase: 'dist', + quiet: false, + noInfo: false, + hot: true, + inline: true, + stats: baseConfig.stats, + proxy: { + '/api': 'http://back:3000', + '/*/api/**': { + target: 'http://back:3000', + pathRewrite: {'^/[\\w-]+': ''} + } + } + } +}; + +let mrgConfig = mode === 'development' ? devConfig : prodConfig; +module.exports = merge(baseConfig, mrgConfig); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 949136459..f107b850e 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -197,5 +197,8 @@ "Booking completed": "Booking complete", "The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation", "You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets", - "Try again": "Try again" + "Try again": "Try again", + "keepPrice": "keepPrice", + "Cannot past travels with entries": "Cannot past travels with entries", + "The notification subscription of this worker cant be modified": "The notification subscription of this worker cant be modified" } diff --git a/webpack.config.js b/webpack.config.js index a102b838e..e526ac118 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -146,9 +146,9 @@ let devConfig = { inline: true, stats: baseConfig.stats, proxy: { - '/api': 'http://localhost:3000', + '/api': 'http://back:3000', '/*/api/**': { - target: 'http://localhost:3000', + target: 'http://back:3000', pathRewrite: {'^/[\\w-]+': ''} } }