I want to "bundle" some files into a single js file, but I don't want the webpack wrapper.
Let's say I have 3 files :
// file1.js
export default { hello: "hello" };
// file2.js
export default { world: "world" };
// index.js
import file1 from "./file1";
import file2 from "./file2";
(() => ({ ...file1, ...file2 }))()
I want the following result :
// build/output.js (+ babel...)
(function(){
return Object.assign({}, { hello: "hello" }, { world: "world" });
})()
Not a single line of code apart from the above build output.
Is it possible with webpack? Thanks!
O.P. Solution
Ok, so I found a solution!
It's maybe not the best one, but it works.
I found this library here which concatenates files together.
Building
I can build using : npm run build.
And the code that concatenates the files is :
// ======================================================
// Tools / Bundle
// ======================================================
// Libs
var path = require("path");
var bundle = require("bundle-js");
module.exports.exec = function() {
// Disable logging (hack for 'bundle-js' library).
var _log = console.log;
console.log = function() {};
// Concatenate each file (required by the application).
var file = path.resolve(__dirname, "../src/index.js");
var bundledCode = bundle({
entry: file,
print: false,
disablebeautify: true
});
// Enable logging.
console.log = _log;
// Return bundled code.
return bundledCode;
};
For some reasons, bundle-js always outputs something even with the option { print: false }. So I added a small hack to fix this.
Related
I wondering if it possible to load script dynamic by webpack.
I create a nodejs application with webpack 5 configuration and typescript. (ts-loader).
And I want the application will be able to run module-script file that I'll give by reading the config file I created.
Something like that:
app.ts:
const config = JSON.parse(fs.readFileSync('./app.config.json', 'utf-8'));
import(config.fn).then(m => { console.log({ m }); m.foo(); })
app.config.json:
{ fn: 'path/to/fn.js' }
path/to/fn.js:
exports = function foo() { console.log('in foo'); }
The problem is when I type the filename hard-coded in import it's works, but when i pass using variable it not work.
I wondering if webpack can do it? because I getting error:
uncaught (in promise) Error: Cannot find module './foo.js'
The code:
console.clear();
const foo = './foo.js';
// not works :(
import(foo).then((m) => {
console.log({ m });
});
// not works :(
// require(foo).then((m) => {
// console.log({ m });
// });
//works:
// import('./foo').then((m) => {
// console.log({ m });
// });
stackblitz
I am writing a custom webpack loader to remove unnecessary code that Terser can't pick up.
Here's the sample source output from webpack loader;
const SvgsMap = {
map1: () => {
return '1';
},
map2: () => {
return '2';
},
map3: () => {
return '3';
},
// ...redacted
map100: () => {
return '100';
},
}
Note that above comes into the loader as a string. And I have a whitelist of string[] as which of them that should be included in the build output;
const whitelistsArr = ["map1"]
I am currently writing a webpack loader to pre-process this before getting into bundled. Which currently uses Node VM that I assume could parse it to javascript object, in which then I can remove some of the unused properties in SvgsMap, then output it back again as a string.
My question is;
Am I doing it the right way with Loader to remove them? Or is it actually a webpack plugin job to do this? Any other alternatives?
I am hitting a rock doing this with VM, It seems like it's unable to mutate the existing code and output it back as a string. Am I wrong here?
Any suggestion is appreciated.
Here's my loader's code so far;
const path = require( 'path' );
const { loader } = require( 'webpack' );
const vm = require( 'vm' );
const whitelists = ['frame21Web'];
const loaderFn = function ( source ) {
/** #type {loader.LoaderContext} */
// eslint-disable-next-line babel/no-invalid-this
const self = this;
const filename = path.basename( self.resourcePath );
const templateWithoutLoaders = filename.replace( /^.+!/, '' ).replace( /\?.+$/, '' );
const vmContext = vm.createContext( { module: {} } );
let newSource = '';
try {
const vmScript = new vm.Script( source, { filename: templateWithoutLoaders } );
const cachedData = vmScript.createCachedData();
console.log(cachedData.toString()); // Doesn't seem to output as a string.
}
catch (err) {
console.error(err);
}
console.log( 'loader', filename, source );
process.exit( 0 );
return source;
};
module.exports = loaderFn;
There may be a couple answers to this question. Difficult to know without knowing the reasoning behind the removal.
If you have control of the file, you could use a combination of Webpack's Define plugin, and some if/else logic. For example
// in your WP config file
new webpack.DefinePlugin({
IS_CLIENT: JSON.stringify(true), // will only be true when compiled via WP
});
// in your module
if (process.env.IS_CLIENT) {
SvgsMap.map1 = () => '1';
}
The above pattern allows for adding/removing chunks of code for your Client bundle, while also allowing for use on the Server.
The other option would be to write a custom Babel plugin (not a WP plugin). I found this article helpful in the past, when I had to write some plugins. Babel gives you more control of how the parts of a JS file are processed, and you can use that plugin outside of WP (like while running Unit tests).
I want to manipulate a file after it has been processed by webpack and babel. There's an emit hook that is triggered just before a new file is saved, but I couldn't see a way to manipulate the file contents. So I settled for using the afterEmit hook to read in the just-written file, modify it, and write it back out:
plugins: [
new class OutputMonitor {
apply(compiler) {
compiler.hooks.afterEmit.tap('OutputMonitor', compilation => {
if (compilation.emittedAssets.has('index.js')) {
let contents = fs.readFileSync('./dist/web/index.js', 'utf-8');
// Strip out dynamic import() so it doesn't generate warnings.
contents = contents.replace(/import(?=\("tseuqer-yb")/, 'console.log');
// Strip out large and large-alt timezone definitions from this build.
contents = contents.replace(large, 'null');
contents = contents.replace(largeAlt, 'null');
fs.writeFileSync('./dist/web/index.js', contents);
}
});
}
}()
],
This gets the job done, but is there a better way?
From what I can tell, you're basically replacing some strings with another strings.
I believe you can use processAssets hook if you're running webpack 5.
Here's an example you can adapt to your case:
const { Compilation, sources } = require('webpack');
class Replace {
apply(compiler) {
compiler.hooks.thisCompilation.tap('Replace', (compilation) => {
compilation.hooks.processAssets.tap(
{
name: 'Replace',
stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE,
},
() => {
// get the file main.js
const file = compilation.getAsset('main.js');
// update main.js with new content
compilation.updateAsset(
'main.js',
new sources.RawSource(file.source.source().replace('a', 'b'))
);
}
);
});
}
}
module.exports = {
entry: './wp.js',
plugins: [new Replace()],
};
I need to have a value set in the window object before a dependency is imported. Say I have this code
// foo.test.js
import { dependency } from './foo'
describe('...', () => {
it('...', () => {
// use dependency
})
})
But for dependency to be imported I need to have a value defined in window.myValues
// foo.js
export const dependency = {
key: window.myValue.nestedValue
}
That code will give me an error when importing the file because window.myValue.nestedValue is trying to access the property nestedValue of undefined.
How can I get that done?
Edit
Following christianeide's answer below I get the following error
● Test suite failed to run
TypeError: Cannot convert undefined or null to object
2 | delete global.window.myValue
3 | global.window = Object.create(window)
> 4 | global.window.myValue = {
| ^
5 | nestedValue: 'someValue'
6 | }
7 | }
at module.exports (jest.setup.js:4:17)
at node_modules/#jest/core/build/runGlobalHook.js:82:17
at ScriptTransformer.requireAndTranspileModule (node_modules/#jest/transform/build/ScriptTransformer.js:684:24)
at node_modules/#jest/core/build/runGlobalHook.js:72:27
at pEachSeries (node_modules/p-each-series/index.js:8:9)
at async _default (node_modules/#jest/core/build/runGlobalHook.js:58:5)
at async runJest (node_modules/#jest/core/build/runJest.js:345:5)
es6 imports are "hoisted", meaning wherever you write them in the code, they'll get processed before the importing module is executed, so the imported module is always executed before the importing module. In your case, this means foo.js executes before foo.test.js, so even if you properly mock your property of window in your tests, foo.js will not see your mock.
You can get around this by using require in your tests to import foo.js after the property of window has been mocked.
// foo.test.js
window.myValue = { nestedValue: MOCK_NESTED_VALUE };
const { dependency } = require('./foo');
describe('...', () => {
it('...', () => {
// use dependency
})
})
As other answers have pointed out, if myValue is one of existing "sytem" properties of window such as window.location, you might have to delete it first. When deleting, don't forget to back it up so that you can restore it when cleaning up after your test.
Try assigning values to global.window.
Something like this:
delete global.window.myValue;
global.window = Object.create(window);
global.window.myValue = {
nestedValue: 'someValue',
};
This can be done inside jest.setup.js, but you could also probably define the values inside foo.test.js
I was able to append properties on the window object by creating a setup.js:
global.propertyA = () => {};
global.nestedPropertyB = {
propertyC: () => {}
};
And setting the file in setupFilesAfterEnv in my jest.config.js file:
module.exports = {
setupFilesAfterEnv: ['<rootDir>/tests/js/setup.js']
}
I have solved such an issue with jest.mock: as it's hoisted above import statements, it's possible to execute any code before and after importing an actual module.
In my case I needed to test some functionality and set node env to a value, different from 'test', and, as I needed this substitution only for the time of import, I set its value back to 'test' before returning the actual module. Of course, it may vary depending on use cases - it's just an example of executing code before and after importing is done. Also i tried to use a getter for a particular module field, and it also worked.
import ApiBase from "../ApiBase";
jest.mock('../ApiBase', () => {
process.env.NODE_ENV = '';
const actual = jest.requireActual('../ApiBase');
process.env.NODE_ENV = 'test';
return actual;
});
I'm trying to having multiple json files imported to a js array
// module.js
module.exports={
english:require("./englishfile"),
chinese:require("./chineseFile"),
french:require("./frenchFile"),
spanish:require("./espFile"),
};
//js file
let allData=require("./module.js");
What this is doing is having all the files in a single array entry. I'm trying to have them as separate array entries for the entire size of module.js . I also would have a much larger number of files in module.js so I don't know its size and wouldn't be able to hard code them
You can do something like this
const requireModule = require.context('.',false,/\.json$/)
const modules = {}
requireModule.keys().forEach(filename =>
{
const moduleName = fileName.replace(/(\.\/|\.json)/g, '');
modules[moduleName] = requireModule(fileName)
OR
modules[moduleName] =
{
namespaced: true,
...requireModule(fileName)
}
});
export default modules;