Different builds based on targeting client vs server code - node.js

I currently have 2 separate webpack builds for server rendered vs client rendered code. Is there an easy way to change the build output based on server/client build?
For example something like this:
// Have some code like this
if(is_client){
console.log('x.y.z')
} else {
server.log('x.y.z')
}
// Webpack outputs:
// replaced code in client.js
console.log('x.y.z')
// replaced code in server.js
server.log('x.y.z')

Have you tried anything like this?
// webpack.config.js
module.exports = () => ['web', 'node'].map(target => {
const config = {
target,
context: path.resolve('__dirname', 'src'),
entry: {
[target]: ['./application.js'],
},
output: {
path: path.resolve(__dirname, 'dist', target),
filename: '[name].js'
},
modules: { rules: ... },
plugins: [
new webpack.DefinePlugin({
IS_NODE: JSON.stringify(target === 'node'),
IS_WEB: JSON.stringify(target === 'web'),
}),
],
};
return config;
});
// later in your code
import logger from 'logger';
if (IS_NODE) {
logger.log('this is node js');
}
if (IS_WEB) {
console.log('this is web');
}
how the compilation works?
// client.bundle.js
import logger from 'logger';
// DefinePlugin creates a constant expression which causes the code below to be unreachable
if (false) {
logger.log('this is node js');
}
if (true) {
console.log('this is web');
}
Finally you will produce your build in production mode, so webpack will include a plugin called UglifyJS, this has a feature called dead code removal (aka tree shaking), so it will delete any unused/unreachable code.
and the final result will look like:
// node.bundle.js
import logger from 'logger';
console.log('this is node js');
//web.bundle.js
console.log('this is node js');

Related

Is it possible to collect coverage on code loaded with vm.runInContext with Jest?

I'm working on a legacy JS project which is not using any require/import. When deploying, the files are just concatenated and the result is sent to a server.
In order to write tests with jest, I created a custom environment to load all the JS files in the global context so that I can call the functions in the test file.
For example:
src/index.js
function sum(x, y) {
return x + y;
}
src/index.spec.js
it('should sum two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
jest.config.js
module.exports = {
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: [
"src/**/*.js",
],
coverageDirectory: "coverage",
coverageProvider: "v8",
testEnvironment: "./jest.env.js",
};
jest.env.js
const NodeEnvironment = require('jest-environment-node').TestEnvironment;
const fs = require('fs');
const vm = require("vm");
const path = require("path");
class CustomEnv extends NodeEnvironment {
constructor(config, context) {
super(config, context);
this.loadContext();
}
loadContext() {
const js = fs.readFileSync('./src/index.js', 'utf8');
const context = vm.createContext(this.global);
vm.runInContext(js, context, {
filename: path.resolve('./src/index.js'),
displayErrors: true,
});
Object.assign(this.global, context);
}
}
module.exports = CustomEnv;
When I run npx jest, the test is executed but the coverage is empty...
Any idea on how to fix the coverage?
I've created a minimal reproducible repo here: https://github.com/GP4cK/jest-coverage-run-in-context/tree/main. You can just clone it, run npm i and npm t.
Note: I'm happy to change v8 to babel or load the context differently if it makes it easier.

No coverage information is generated for vscode extension using nyc

To generate the code coverage report for vscode extension, i am using nyc and running those via vscode test runner.
Source : https://code.visualstudio.com/api/working-with-extensions/testing-extension
Project structure:
out
-test
-unit
-testcases.js
-index.js
- runTest.js
``
"test": "rm -rf .nyc_output/ && nyc node ./out/test/runTest.js",
"nyc": {
"extends": "#istanbuljs/nyc-config-typescript",
"require": [
"ts-node/register",
"source-map-support/register"
],
"report-dir": ".",
"reporter": [
"text",
"html",
"lcov"
],
"exclude": ["out/test/**"],
"include": [ "out/**/*.js" ],
"check-coverage": true
},
index.ts file:
import * as path from 'path';
import * as Mocha from 'mocha';
import * as glob from 'glob';
export function run(): Promise<void> {
const mocha = new Mocha({
ui: 'tdd',
color: true,
timeout: 20000,});
const testsRoot = path.resolve(__dirname, '../unit');
return new Promise((c, e) => {
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
if (err) {
return e(err);
}
// Add files to the test suite
files.forEach(f => {
mocha.addFile(path.resolve(testsRoot, f));
});
try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
e(err);
}
});
});
}
runTest.ts file:
import * as path from 'path';
import { runTests } from 'vscode-test';
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
// The path to test runner
// Passed to --extensionTestsPath
//const extensionTestsPath = path.resolve(__dirname, './unit/index-coverage');
const extensionTestsPath = path.resolve(__dirname, './unit/index');
// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
} catch (err) {
//console.error('Failed to run tests');
process.exit(1);
}
}
main();
I was not able to generate code coverage report.It generates report but without any information.
What i am doing wrong here??
There are couple of ways to do this. I found some valuable information while checking below link:
How do a generate vscode TypeScript extension coverage report
Seems the easiest one is from user frenya. but the other two also gives valuable information.

NextJS with react-svg-loader fails

I'm having trouble getting my configuration right for this. I have a NextJS setup with next-css and I'm trying to add react-svg-loader to the configuration:
next.config.js:
const withCSS = require("#zeit/next-css");
module.exports = withCSS({
cssModules: true,
cssLoaderOptions: {
importLoaders: 1,
localIdentName: "[local]__[hash:base64:4]"
},
webpack(config, options) {
const { dev, isServer } = options;
config.module.rules.push({
test: /\.svg$/,
use: [
{
loader: "react-svg-loader",
options: {
jsx: true // true outputs JSX tags
}
}
]
});
return config;
}
});
The svgs will still fail to load:
{ Error: (client) ./svgs/pencil.svg 10:9 Module parse failed:
Unexpected token (10:9) You may need an appropriate loader to handle
this file type.
Looks like my config above doesn't work but I can't quite figure out why.

Webpack externals in both node and the browser

I have an isomorphic React application which runs in both the browser and on the server. I build the same code for both by running two separate Webpack builds through two different entry points and with different configs.
The problem is that the external file that exists on the browser window via an external script tag (Google Maps in this instance) obviously won't exist when running in node on the server. The code is identical except the entry point file.
index.html:
// index.html
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=XXX"></script>
Simplified config:
// Server Webpack config
{
entry: 'server.js',
target: 'node',
externals: {
google: google
}
}
// Client Webpack config
{
entry: 'client.js',
target: 'browser',
externals: {
google: google
}
}
The Component:
// The view which builds and runs fine in
// the client but doesn't run on the server.
var React = require('react'),
css = require('./style.css'),
google = require('google'); // Nope, not on the server obviously!
var Component = React.createClass({
render: function () {
return (
<div>
// Do maps stuff
</div>
);
}
});
module.exports = Component;
My question is how should I handle this?
Error: Cannot find module 'google'
I currently have a solution which I'm not at all keen on.
// Server Webpack config
{
entry: 'server.js',
target: 'node',
externals: {
google: google
},
plugins: [
new webpack.DefinePlugin({ 'ENV.browser': false }),
]
}
// Client Webpack config
{
entry: 'client.js',
target: 'browser',
externals: {
google: google
},
plugins: [
new webpack.DefinePlugin({ 'ENV.browser': true }),
]
}
// The component
var React = require('react'),
css = require('./style.css');
if (ENV.browser) {
var google = require('google');
}
var Component = React.createClass({
render: function () {
return (
<div>
if (ENV.browser) {
// Do maps stuff
}
</div>
);
}
});
module.exports = Component;
You can use NormalModuleReplacementPlugin to replace the module with a noop, as per an idea from Dustan Kasten:
{
plugins: [
new webpack.NormalModuleReplacementPlugin(/^google$/, 'node-noop'),
],
}

requirejs optimization with gulp

I am using requirejs and gulp to build angular app. I am using amd-optimize and gulp-requirejs-optimize to add all js files into single file. Here is my main.js file:
require.config(
{
paths: {
app : 'app',
angular : '../bower_components/angular/angular',
jquery : '../bower_components/jquery/dist/jquery',
angularResource : '../bower_components/angular-resource/angular-resource',
angularRoute : '../bower_components/angular-route/angular-route',
publicModule : 'public_module',
route : 'route'
},
shim: {
'app': {
deps: ['angular']
},
'angularRoute': ['angular'],
angular : {exports : 'angular'}
}
}
);
And gulpfile.js
var gulp = require('gulp');
var rjs = require('gulp-requirejs');
var connect = require('gulp-connect');
var requirejsOptimize = require('gulp-requirejs-optimize');
var amdOptimize = require('amd-optimize');
var concat = require('gulp-concat');
// using amd-optimize.
gulp.task('bundle', function () {
return gulp.src('app/**/*.js')
.pipe(amdOptimize('main'))
.pipe(concat('main-bundle.js'))
.pipe(gulp.dest('dist'));
});
// using gulp-requirejs-optimize.
gulp.task('scripts', function () {
return gulp.src('app/main.js')
.pipe(requirejsOptimize())
.pipe(gulp.dest('dist'));
});
When I run gulp bundle or gulp scripts, it shows me same content of main.js file in output file(not showing all js template in one output file).
The output file is:
require.config({
paths: {
angular: '../bower_components/angular/angular',
jquery: '../bower_components/jquery/dist/jquery',
angularResource: '../bower_components/angular-resource/angular-resource',
angularRoute: '../bower_components/angular-route/angular-route',
publicModule: 'public_module',
route: 'route'
},
shim: {
'app': { deps: ['angular'] },
'angularRoute': ['angular'],
angular: { exports: 'angular' }
}
});
define('main', [], function () {
return;
});
How can I configure gulp to put every js template into one js file?
check the docs for all the options for amdoptimize. For example you can point to your config file or add paths.
I always have trouble getting all the paths to line up, so make sure to check them diligently.
here is how you can start to put the options in:
gulp.task('requirejsBuild', function() {
gulp.src('app/**/*.js',{ base: 'app' })
.pipe(amdOptimize("app",{
baseUrl: config.app,
configFile: 'app/app-config.js',
findNestedDependencies: true,
}))
.pipe(concat('app.js'))
.pipe(gulp.dest('dist'))
});
You are not requiring any files - you just define an empty module named main.
You need to kick off you app by requiring a module, eg.
require(['app'], function (App) {
new App().init();
});

Resources