Currently I am implementing webpack in our websites. The websites are static websites build on a php Zend2 architecture. We have lots and losts of vendor module installed and updated by composer. I have a working webpack solution, hence I feel like the code for legacy minification could be better. I have the following constraints
Implement the old js code as is. I know rewriting to modules would be the best options but we have 100+ js files. The plan is to rewrite if the code needs to be touched.
Legacy code should be minified based on a env.NODE_ENV flag
current code splitting needs to be maintained
Currently the solution works as follow
Webpack switches to different js-entry files
module.exports = env => {
// webpack variables
const isProduction = env.NODE_ENV === 'production';
const assetJsPath = 'module/Eurocampings/assets/js/';
const assetJsLegacyPath = 'module/Eurocampings/assets/js-legacy/';
const webpackEntrySuffix = isProduction ? 'production' : 'develop';
const paths = {
entry: {
main: `${assetJsPath}main.js`,
mainLegacy: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/main-legacy.js`,
vendorBase: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/vendor-base.js`,
homepage: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/homepage.js`,
campsiteDetails: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/campsite-details.js`,
campsiteCompare: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/campsite-compare.js`,
campsiteChainDetails: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/campsite-chain-details.js`,
ajaxsolr: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/ajaxsolr.js`,
campsiteSearch: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/campsite-search.js`,
campsiteExtendedSearch: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/campsite-extended-search.js`,
picturefill: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/picturefill.js`,
destination: `${assetJsLegacyPath}webpack-entries-${webpackEntrySuffix}/destination.js`,
styles: './module/Eurocampings/assets/scss/style.scss',
},
output: {
folder: 'module/Eurocampings/dist',
}
};
Example of develop legacy entry without uglify-loader!
// webpack entry for develop
import exec from 'script-loader!../AcsiCampsiteSearch/DataLayerWidgetFactory.js';
import exec from 'script-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/acsiCampsiteCompare.js';
import exec from 'script-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/extensions/Label.js';
import exec from 'script-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/extensions/Selector.js';
import exec from 'script-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/extensions/CompareBox.js';
import exec from 'script-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/extensions/Cookie.js';
import exec from 'script-loader!../../../../../vendor/acsi/acsi-campsite-search/assets/js/search.js';
import exec from 'script-loader!../../../../../vendor/acsi/acsi-campsite-search/assets/js/map_initialization.js';
Example of production entry with uglify-loader!
// webpack entry for develop
import exec from 'script-loader!uglify-loader!../AcsiCampsiteSearch/DataLayerWidgetFactory.js';
import exec from 'script-loader!uglify-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/acsiCampsiteCompare.js';
import exec from 'script-loader!uglify-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/extensions/Label.js';
import exec from 'script-loader!uglify-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/extensions/Selector.js';
import exec from 'script-loader!uglify-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/extensions/CompareBox.js';
import exec from 'script-loader!uglify-loader!../../../../../vendor/acsi/acsi-campsite/assets/js/acsiCampsiteCompare/extensions/Cookie.js';
import exec from 'script-loader!uglify-loader!../../../../../vendor/acsi/acsi-campsite-search/assets/js/search.js';
import exec from 'script-loader!uglify-loader!../../../../../vendor/acsi/acsi-campsite-search/assets/js/map_initialization.js';
Related
I created a cli application which reads its version number from package.json with this bit of code
const packageJson = JSON.parse(fs.readFileSync(path.resolve('./package.json'), 'utf8'))
This works fine if I run the app with yarn start or a similar command while development
But after the package is installed with npm install --global app-name the user should use the declare executable from any path on the system. So if I want to run it say in /Users/myUser/Desktop I get an error like this
Error: ENOENT: no such file or directory, open '/Users/myUser/Desktop/package.json'
So what's a good protocol of loading this package.json within my CLI or is there a better way for approaching this?
Later edit:
For clarity, my package json contains this
{
...
"bin": {
"clip": "./bin/clip.js"
},
...
}
so what I mean by my problem, is when I am running the executable "clip" from a different path, after I used npm publish
After some research I tried this code (use the path.dirname function):
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
export const packageJsonLocation = path.join(__dirname, './../package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonLocation, 'utf8'))
and this (just importing the file as json using node's standard import keyword)
import * as packageJson from './../package.json' assert { type: 'json' }
in both cases I get the same result, the executable generated and it tries to read package.json from the current directory. Specifically if I try to console.log() the path I get my current path where I am executing the global executable (clip in my case)
Use __dirname because it always refers to the path of the file that contains this variable, whereas ./ gives you the working directory, such as process.cwd().
const packageJson = JSON.parse(fs.readFileSync(
path.join(__dirname, 'package.json'), 'utf8')
)
If you're using ES Modules, do also to get __dirname
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const packageJson = JSON.parse(fs.readFileSync(
path.join(__dirname, 'package.json'), 'utf8')
)
Edit:
You installed the package globally with a bin, but the bin you're calling with a CLI is a symlink which is inside the path <npm_glob_path>/node_modules/bin not <npm_glob_path>/node_modules/app-name/bin. The package.json of your app is inside <npm_glob_path>/node_modules/app-name. And don't use ./, always use path calls
Hence try this instead (replace app-name by your app's name):
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url))
console.log('__dirname:' + __dirname) // TELL ME WHAT YOU SEE HERE WHEN YOU RUN THE CLI CMD
const packageJsonLocation = path.join(__dirname, '..', 'app-name' 'package.json')
const packageJson = JSON.parse(fs.readFileSync(
path.join(__dirname, 'package.json'), 'utf8')
)
And please, add console.log('__dirname:' + __dirname) after defining __dirname. Which path do you see when you run the CLI app?
is there a better way for approaching this?
Yes - you should store the version number in the actual package itself somewhere. This way it will always be available/accessible and there's no risk of the package.json version and the installed version becoming out of sync. For example, if someone adds your package to a project and then runs yarn install, but later uses git pull to get an up-to-date version of their local files which happens to include a version bump for your package, there is a window where the package.json has a different version number to the installed version.
I'm trying to create my own package that will initialize the boiler plate project by running node package execute npx. I once created one using node.js and inquirer and it worked when I run:
npx ts-config-files.json
This is the package that I've created and published on npm that generates tsconfigs.
The one that I've just created when i run the command:
npx nodejs-backend
I get this:
npx: installed 135 in 47.951s
command not found: nodejs-backend
Instead of getting some inquirer messages. The code of what i'm working on can be found here.
I've been thinking it's because I'm using typescript but when i changed to javascript it's still the same thing.
Sample code of the main file server.ts:
#!/usr/bin/env node
import path from "path";
import inquirer from "inquirer";
import { writeFile, readFile } from "fs/promises";
import fs from "fs";
import helperFunction from "./constants";
import { exec } from "child_process";
import { objJS, objTS } from "./utils";
import chalk from "chalk";
helperFunction.prompt();
const cwd = process.cwd();
const base_name = path.basename(cwd); // node
const main = async () => {
const baseDir: string = "src";
let fileName: string = "";
let packageObject: typeof objTS | typeof objJS;
const { language } = await inquirer.prompt([
{
choices: ["JavaScript", "TypeScript"],
type: "checkbox",
default: "TypeScript",
name: "language",
message: "which language do you want to use for your backend app?",
},
]);
....
You forgot to add:
"bin": {
"nodejs-backend": "server.ts"
},
Which should be in package.json.
Also after that make sure to npm link in your repo before trying to use npx otherwise it will try install it from the https://registry.npmjs.org registry instead of local.
That being said I'm fairly certain it won't execute as typescript even if you've done that (which isn't your issue right now but it will be). For that you can install ts-node and use #! /usr/bin/env ts-node.
I developed a React Native module (wrapping an SDK) and I’m interested in creating some unit tests using mocha. I’m not very familiar with mocha, but I can’t exactly figure out how to proceed.
I have my react native module, call it react-native-mymodule which I can use in an app by doing:
npm install react-native-mymodule
react-native link react-native-mymodule
Then I can import my module with:
import MySDK from "react-native-mymodule”;
I’m trying to do a similar thing with unit tests. In my root directory I have a test/ directory which is where I want to hold all my unit tests.
My simple test file in test/sdk.tests.js
import MySDK from "react-native-mymodule”;
var assert = require('assert');
describe(‘MySDK’, function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal([1, 2, 3].indexOf(4), -1);
});
});
});
I’ve tried modifying a tutorial I found online on compiling modules, but haven’t had any luck. This is a file test/setup.js:
import fs from 'fs';
import path from 'path';
import register from 'babel-core/register';
const modulesToCompile = [
'react-native-mymodule’
].map((moduleName) => new RegExp(`${moduleName}`));
const rcPath = path.join(__dirname, '..', '.babelrc');
const source = fs.readFileSync(rcPath).toString();
const config = JSON.parse(source);
config.ignore = function(filename) {
if (!(/\/node_modules\//).test(filename)) {
return false;
} else {
return false;
}
}
register(config);
.babelrc in the root level of my module
{
"presets": ["flow", "react-native"],
"plugins": [
["module-resolver", {
"root": [ "./js/" ]
}]
]
}
I have a test/mocha.opts file:
--require babel-core/register
--require test/setup.js
I’m invoking mocha with: ./node_modules/mocha/bin/mocha and I get an error:
Error: Cannot find module 'react-native-mymodule'
Can anyone advise me on the best way to test react native modules?
If you want to test native modules, I suggest the following:
1. E2E Tests
Node.js standalone cannot interpret native modules. If you want to test native modules in the context of your app, you want to create e2e tests using appium/webdriverio instead of writing unit tests with mocha.
With this, you actually start an emulator with your app installed.
Resources:
http://appium.io/docs/en/about-appium/intro/?lang=de
https://medium.com/jetclosing-engineering/react-native-device-testing-w-appium-node-and-aws-device-farm-295081129790
https://medium.com/swlh/automation-testing-using-react-native-and-appium-on-ubuntu-ddfddc0c29fe
https://webdriver.io/docs/api/appium.html
2. Unit Tests
If you want to write unit tests for your native module, write them in the Language the Native Module is written in
Resources:
https://www.swiftbysundell.com/basics/unit-testing/
https://junit.org/junit5/
https://developer.apple.com/documentation/xctest
https://cmake.org/cmake/help/latest/manual/ctest.1.html
Other than that, you have to mock the modules.
https://jestjs.io/docs/en/mock-functions
https://sinonjs.org/releases/latest/mocks/
https://www.npmjs.com/package/mock-require
Node.JS 10 added experimental support for loading ES6 modules, which already work in browsers. That would mean that we could finally use exactly the same files for Node.JS and browsers without any transpiling or polyfills.
Except we can't. Node.js requires .mjs extension for files to be loaded as modules. I tried tricking node by using a symlink, but node got around it:
D:\web\lines>node --experimental-modules ES6test.mjs
(node:7464) ExperimentalWarning: The ESM module loader is experimental.
D:\web\lines\ES6test.js:6
import myLibrary from "./MyFile.mjs";
^^^^^^^^^^^^^^^
I can't think of any other workaround to make this work - which really renders the whole ES6 module support useless.
Can anybody else think of some trick to make Node.js ignore the extension?
You can now import .js file in node v12.x, in 2 steps:
Add the following line in your package.json file:
// package.json
{
"type": "module"
}
Add --experimental-modules flag before the script:
node --experimental-modules index.js
Reference: https://nodejs.org/api/esm.html
Node.js requires all ES modules should have .mjs extension. Since Node.js support of ES modules is experimental, this is subject to change. A proposal and open pull request are expected to address this problem with package.json esm flag and --mode option.
Currently this can be solved with custom ES module loader that hooks into default module resolver and changes module type for some modules:
custom-loader.mjs
import path from 'path';
const ESM_WITH_JS_EXT = './MyFile.js'; // relative to loader path
const ESM_WITH_JS_EXT_URL = new URL(path.dirname(import.meta.url) + `/${ESM_WITH_JS_EXT}`).href;
export function resolve(specifier, parentModuleURL, defaultResolver) {
const resolvedModule = defaultResolver(specifier, parentModuleURL);
if (resolvedModule.url === ESM_WITH_JS_EXT_URL)
resolvedModule.format = 'esm';
return resolvedModule;
}
It is used as:
node --experimental-modules --loader ./custom-loader.mjs ./index.mjs
Since there are fundamental differences in how ES and CommonJS modules are evaluated, the changes should be limited to modules that need them.
I solved exactly this problem with the fabulous esm package. You can enable dynamic (smart) esm module loading package wide, or per run with a flag like this:
node -r esm your/es6/module.js
It also has options to treat every file as a es6 module, or only those ending in '.mjs'. There are other packages out there, but this one just worked.
Import and export modules using ES6 that work with Node.js
Name files with .mjs extension instead of .js
Create files
touch main.mjs lib.mjs
main.js
import { add } from './lib.mjs';
console.log(add(40, 2));
lib.mjs
export let add = (x,y) => {
return x + y
}
Run
node --experimental-modules main.js
Here is a module that does what you need esmjs.mjs
import { readFileSync } from 'fs'
import { fileURLToPath, pathToFileURL } from 'url'
import { dirname, join } from 'path'
export const jsmodule = (test_url_or_path, module_path) => {
const __filename = test_url_or_path.toLowerCase().startsWith('file:')
? fileURLToPath(test_url_or_path)
: test_url_or_path
const __dirname = dirname(__filename)
const abs_path = join(__dirname, module_path)
const file_url = pathToFileURL(abs_path)
const file_buf = readFileSync(file_url)
const b64 = file_buf.toString('base64')
const moduleData = "data:text/javascript;base64," + b64
return import(moduleData)
}
Usage from .mjs module:
const { hey } = await jsmodule(import.meta.url, '../../test-data/mjs.js')
Usage, from .js file:
const { hey } = await jsmodule(__filename, '../../test-data/mjs.js')
Reference & tests on Github
You can do it in this way:
Create a file module2.mjs (the name is up to you)
'use strict';
export function foo() {
return 'foo';
}
Create index.mjs file:
'use strict';
import { foo } from './module2.mjs';
console.log(foo());
Using node 8.11.1 (or 10) you can run it as (command and output provided):
node --experimental-modules index.mjs
(node:60428) ExperimentalWarning: The ESM module loader is experimental.
foo
I have same issue but with local component.
That's my tree:
app
|
- assets
|
- js
|
- app.jsx
|
- MainComponent.jsx
But when I run command:
macpro:app user$ browserify assets/js/app.jsx > tesapp.js
It response error :
Error: Cannot find module 'MainComponent' from
'/Users/user/WebstormProjects/gowithme/app/assets/js'
That is code of app.jsx file :
var React = require('react');
var ReactDOM = require('react-dom');
var MainComponent = require('MainComponent');
console.log("Test",MainComponent);
It happens because you are using .jsx extension. By default browserify cannot recognize unknown extension. Add browserifyOptions to you grunt config, it might help:
options: {
transform: [['babelify', {presets: ['es2015', 'react']}]],
browserifyOptions: {
debug: true,
extensions: ['.jsx']
}
}
Also I suggest to omit usage of .jsx. No .jsx extension?
You need to give the relative path when you're loading a local module (i.e. a .js file of your own) with require:
var React = require('react');
var ReactDOM = require('react-dom');
var MainComponent = require('./MainComponent'); //added ./ so now the path is relative
console.log("Test",MainComponent);
When using an absolute path (like require('fs') or require('react')) node will try to load native modules (like fs, path etc) or modules inside node_modules.
https://nodejs.org/api/modules.html#modules_modules