webpack, restrict what can be imported - javascript

Is there a way in webpack to restrict what files can be imported?
Say I want to be able to import files that are in the same directory, as well as the parent directory, but nothing above that parent directory? For example:
These work
import { blah } from "./script.js";
import { blah2 } from "./../gui/textbox.js";
import { blah3 } from "./../I/can/go/as/deep/down/as/I/want/here.js";
But this wouldn't work
import { passwords } from "./../../passwords.txt";
Because that would go up two (or x number of) directories, instead of just one.

You can create a loader to restrict webpack imports to specific files.
// file: webpack.config.js
const path = require('path');
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve('my-webpack-loader.js'),
options: {/* ... */}
}
]
}
]
}
};
Then throw if the resource file is outside ./src and ./node_modules directory or any directory of your choice.
// file: my-webpack-loader.js
const { getOptions } = require('loader-utils');
const validateOptions = require('schema-utils');
const path = require('path');
const schema = {
type: 'object',
properties: {
test: {
type: 'string'
}
}
};
function handler(source) {
const options = getOptions(this);
if(this.resourcePath.indexOf(path.resolve('./node_modules')) !== 0) {
if(this.resourcePath.indexOf(path.resolve('./src')) !== 0) {
throw `Reseource loading restricted for ${this.resourcePath}`;
}
}
validateOptions(schema, options, 'My Webpack Loader');
return source;
}
module.exports = handler;
For more info see writing a webpack loader.

react-dev-utils has a plugin for this.
This Webpack plugin ensures that relative imports from app's source
directories don't reach outside of it.
var path = require('path');
var ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
module.exports = {
// ...
resolve: {
// ...
plugins: [
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
// ...
],
// ...
},
// ...
};

Related

Problems importing function with webpack

Using webpack I am trying to export a function from domManipulation.js to index.js, they are both in src folder
export function displayPage(pageToDisplay) {
pageToDisplay.classlist.remove("hide");
pagesArray.forEach((page) => {
if (page !== pageToDisplay) {
page.classList.add("hide");
}
});
}
using
import { displayPage } from "./domManipulation.js";
but in index.js vs code just greys out as if the import text isn't working. If I right click on displayPage and select go to definition vs code takes my to the function so I guess it knows where the import statement is pointing to? However if I try and run the function in the chrome console on index.html from the dist folder I get displayPage(ADD_EDIT_PAGE); VM18270:1 Uncaught ReferenceError: displayPage is not defined at <anonymous>:1:1
Please help and be kind I literally started using webpack like 2 days ago!
below is the the webpack config, the full code from the file I am trying to export the function from and the code from the module as it appears in chrome.
const path = require("path");
module.exports = {
// watch: true,
mode: 'development',
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
},
};
const DELETE_TODO_PAGE = document.getElementById("deleteTodoPage");
const CLEAR_COMPLEATED_PAGE = document.getElementById("clearCompleatedPage");
const NOTES_PAGE = document.getElementById("notesPage");
const TODO_PAGE = document.getElementById("todoPage");
const ADD_EDIT_PAGE = document.getElementById("addEditPage");
let pagesArray = [
DELETE_TODO_PAGE,
CLEAR_COMPLEATED_PAGE,
NOTES_PAGE,
TODO_PAGE,
ADD_EDIT_PAGE,
];
export function displayPage(pageToDisplay) {
pageToDisplay.classlist.remove("hide");
pagesArray.forEach((page) => {
if (page !== pageToDisplay) {
page.classList.add("hide");
}
});
}
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "displayPage", function() { return displayPage; });
const DELETE_TODO_PAGE = document.getElementById("deleteTodoPage");
const CLEAR_COMPLEATED_PAGE = document.getElementById("clearCompleatedPage");
const NOTES_PAGE = document.getElementById("notesPage");
const TODO_PAGE = document.getElementById("todoPage");
const ADD_EDIT_PAGE = document.getElementById("addEditPage");
function displayPage(pageToDisplay) {
let pagesArray = [
DELETE_TODO_PAGE,
CLEAR_COMPLEATED_PAGE,
NOTES_PAGE,
TODO_PAGE,
ADD_EDIT_PAGE,
];
pageToDisplay.classlist.remove("hide");
pagesArray.forEach((page) => {
if (page !== pageToDisplay) {
page.classList.add("hide");
}
});
}

Vue Cli 3 is not allowing me to process SVG's in Webpack

Vue Cli defaults to file-loader for SVG assets, but I want to use svg-sprite-loader (as well as a few others) instead.
I updated the vue.config.js file to do this and it still seems to use file-loader. Almost as though it's not picking up my config at all.
vue.config.js
module.exports = {
configureWebpack: {
module: {
rules: [
{
test: /\.(svg)(\?.*)?$/,
use: [
{
loader: 'svg-sprite-loader',
options: {
name: '[name]-[hash:7]',
prefixize: true
}
},
'svg-fill-loader',
'svgo-loader'
]
}
]
}
}
}
Is there anything wrong with my setup?
I'm still getting SVG files imported into my component as a URL string / path when it should be an object with properties.
Many thanks.
This took me a while to find a work around. Basically you need to stop file-loader matching on .svg. The best way I have found to do this is using chainWebpack and returning false from the test method on file-loader. I have included my working config.
module.exports = {
lintOnSave: false,
configureWebpack: {
module: {
rules: [
{
test: /\.(svg)(\?.*)?$/,
use: [
{
loader: 'svg-inline-loader',
options: {
limit: 10000,
name: 'assets/img/[name].[hash:7].[ext]'
}
}
]
}
]
}
},
chainWebpack: config => {
config.module
.rule('svg')
.test(() => false)
.use('file-loader')
}
}
The Webpack docs for Vue CLI 3.0 beta got updated with an example on how to replace an existing Base Loader. For svg-sprite-loader this means that you'll have to add the following configuration to your vue.config.js:
chainWebpack: config => {
config.module
.rule('svg')
.use('file-loader')
.loader('svg-sprite-loader')
}
I'm using Vue CLI 3.0.3 and this config works for me 😉
const path = require('path');
const glob = require('glob');
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
module.exports = {
lintOnSave: false,
configureWebpack: {
plugins: [
new SpriteLoaderPlugin()
]
},
chainWebpack: config => {
config.module.rules.delete('svg');
config
.entry('app')
.clear()
.add(path.resolve(__dirname, './src/main.ts'))
config
.entry('sprite')
.add(...glob.sync(path.resolve(__dirname, `./src/assets/icons/*.svg`)));
config.module.rule('svg')
.test(/\.(svg)(\?.*)?$/)
.use('file-loader')
.loader('svg-sprite-loader')
.options({
extract: true,
spriteFilename: 'icons.svg'
})
}
};
Vue CLI docs for version 3.x in webpack section suggests to use something like this:
// vue.config.js
module.exports = {
chainWebpack: config => {
const svgRule = config.module.rule('svg')
// clear all existing loaders.
// if you don't do this, the loader below will be appended to
// existing loaders of the rule.
svgRule.uses.clear()
// add replacement loader(s)
svgRule
.use('vue-svg-loader')
.loader('vue-svg-loader')
}
}
Even vue-svg-loader configuration guide suggests same approach.
module.exports = {
chainWebpack: config => {
const svgRule = config.module.rule('svg')
svgRule.clear()
svgRule
.use('vue-svg-loader')
.loader('vue-svg-loader')
}
}

How to add a compile-time global variable in webpack?

I've got a dynamically configurable webpack setup, with some custom loaders. Each of these loaders needs a configuration object which is only known dynamically, at compile time, so it's not possible to hard code it in loader's options. How can loaders access this object? Is there a "canonical" way to have a compile-time global in webpack?
Basically, my setup allows for multiple dynamic build-time configurations, it looks like this:
// webpack.config.js
let defaults = {
....
module: {
loaders: [
{
test: '.some.special.stuff',
loader: 'my-own-loader',
}
}
module.exports = function main() {
let BUILD_CONFIG = require(process.env.BUILD_CONFIG);
....
return defaults;
}
So webpack is supposed to be called like BUILD_CONFIG=some-config.js webpack. Note that some-config.js contains a lot of non-webpack stuff, so I can't just return it from main or merge it with defaults
Now, I have my-own-loader in my-own-loader.js:
// my-own-loader.js
module.exports = function main(content) {
...
My question is how to access that BUILD_CONFIG variable from webpack's main in the loader's main.
I think I understand what you are trying to do and have an example project detailing the process.
To start, I'm going to set an environment variable named TEST_VAR through whatever mechanism my platform supports. I'm on macOS, so that's export TEST_VAR=fizzbuzz. My loader.js is able to use that environment variable through the node convention: process.env.TEST_VAR.
Remember, your webpack.config.js is still a node run JavaScript file. You can use all the built-ins that node supports including process.env.
Consider the following files and structure:
Structure
webpack-test
|- package.json
|- webpack.config.js
|- index.html
|- /dist
|- bundle.js
|- /loaders
| obj-loader.js
|- /res
|- /obj
|- dummy.obj
|- /src
|- index.js
package.json
{
"name": "webpack-test",
"version": "0.0.1",
"main": "index.js",
"dependencies": {
"webpack": "^3.8.1"
},
"devDependencies": {
"http-server": "^0.10.0",
"loader-utils": "^1.1.0",
},
"scripts": {
"start": "http-server .",
"build": "webpack",
},
}
webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
resolveLoader: {
modules: [
path.resolve(__dirname, "loaders")
]
},
module: {
rules: [
{
test: /\.obj$/,
use: {
loader: "obj-loader",
options: {
dummyText: "lorem ipsum"
}
}
}
]
}
};
/loaders/obj-loader.js
const loaderUtils = require("loader-utils"),
schemaUtils = require("schema-utils");
function loader(source) {
const options = loaderUtils.getOptions(this);
console.log(options);
let tmp = process.env.TEST_VAR;
if (tmp === undefined) {
console.warn("TEST_VAR is not defined");
}
return `export default function(){return "TEST_VAR: ${tmp} || ${source} || ${options.dummyText}";}`;
}
module.exports = loader;
/res/obj/dummy.obj
Hello
/src/index.js
import dummy from "../res/obj/dummy.obj";
(function () {
"use strict";
function main() {
document.querySelector("p").innerHTML = dummy();
}
document.addEventListener("DOMContentLoaded", main);
}());
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Webpack Test</title>
<script src="dist/bundle.js"></script>
</head>
<bod>
<main>
<h1>Webpack Test</h1>
<p>Lorem Ipsum</p>
</main>
</bod>
</html>
Whenever I build and run this webpage, I get what amounts to:
<main>
<h1>Webpack Test</h1>
<p>TEST_VAR: fizzbuzz || Hello || lorem ipsum</p>
</main>
Where "fizzbuzz" was added from the environment variable, "Hello" the source is from /res/obj/dummy.obj, and "lorem ipsum" is the text that I specified as the dummyText option for the obj-loader. However, it could have just as easily come from an environment variable that I set.
Additional update based on edit
Considering your edit, what about this; instead of setting the rule in defaults set it in the exported function to get scope access. Then add your loader rules based on the new rule syntax: module.loaders is now module.rules. That way you have access to options in your custom loader and thus have access to BUILD_CONFIG.
Newer webpack.config.js
// Hypothetically you run:
// export BUILD_CONFIG=some-config.js
// Which sets:
// process.env.BUILD_CONFIG = "some-config.js";
// New Webpack loader/rules syntax
// https://webpack.js.org/guides/migrating/#module-loaders-is-now-module-rules
let defaults = {
module: {
rules: [
/*
*Set this later in exported function
{
test: ".some.special.stuff",
use: {
loader: "my-own-loader",
options: {
some: "options"
}
}
}
*/
]
}
};
module.exports = function (content) {
let BUILD_CONFIG = require(process.env.BUILD_CONFIG);
// Other code
let dd = defaults;
defaults.module.rules.push({
test: ".some.special.stuff",
use: {
loader: "my-own-loader",
options: {
stuffFromEnvConfig: BUILD_CONFIG
}
}
});
return dd;
};
my-own-loader.js
const loaderUtils = require("loader-utils"),
schemaUtils = require("schema-utils");
module.exports = function main(content) {
const options = loaderUtils.getOptions(this);
console.log(options);
let tmp;
if (options.stuffFromEnvConfig.useFirst) {
tmp = "Fizz";
} else {
tmp = "Buzz";
}
return `export default function(){return "TEST_VAR: ${tmp}";}`;
};

Compiling Webpack in memory but resolving to node_modules on disk

I'm trying to use web pack to compile an in memory string of valid javascript code. I'm using memory fs as outlined here: https://webpack.github.io/docs/node.js-api.html#compile-to-memory.
So I'm taking a string containing raw javascript, writing that to memory fs, and then web pack resolves to that entry point. But the compilation fails on the first require statement, presumably because it's not able to look in the real fs for node_modules.
Any ideas on how can I accomplish this?
import webpack from 'webpack';
import MemoryFS from 'memory-fs';
import thenify from 'thenify';
function* compile(code) {
const fs = new MemoryFS();
fs.writeFileSync('/file.js', code);
const compiler = webpack({
entry: { file: '/file.js' },
output: {
path: '/build',
filename: '[name].js'
},
module: {
loaders: [
{ test: /\.json$/, loader: 'json' }
],
}
});
compiler.run = thenify(compiler.run);
compiler.inputFileSystem = fs;
compiler.resolvers.normal.fileSystem = fs; //this is needed for memfs
compiler.outputFileSystem = fs;
const stats = yield compiler.run();
//retrieve the output of the compilation
const res = stats.compilation.assets['file.js'].source();
return res;
}
Usage
var code = "var _ = require('underscore'); console.log(_);";
var bundle = yield compile(code); //should be a bundle containing the underscore source.
The error is
ModuleNotFoundError: Module not found: Error: Cannot resolve module
underscore in /
This question indicates that others have tried the same thing: https://github.com/webpack/webpack/issues/1562. there's a gist referenced at https://gist.github.com/DatenMetzgerX/2a96ebf287b4311f4c18 that I believe was intended to do what I'm hoping to accomplish, but in it's current form I don't see how. It assigns an instance of MemoryFs to all of the resolvers. I've tried assigning node's fs module, but no dice.
So in short, I'm trying to set an entry point to an in memory string of raw javascript, but still have require and import statements resolved to node_modules on disk.
UPDATE
I've been able to get the result I'm looking for but it's not pretty. I'm basically overriding the implementation of #stat and #readFile in MemoryFS
to check the real filesystem if it gets any request for a file that doesn't exist in memory. I could clean this up a bit by subclassing MemoryFS instead of swapping method implementations at runtime, but the idea would still be the same.
Working solution
import webpack from 'webpack';
import JsonLoader from 'json-loader';
import MemoryFS from 'memory-fs';
import UglifyJS from "uglify-js";
import thenify from 'thenify';
import path from 'path';
import fs from 'fs';
import root from 'app-root-path';
/*
* Provide webpack with an instance of MemoryFS for
* in-memory compilation. We're currently overriding
* #stat and #readFile. Webpack will ask MemoryFS for the
* entry file, which it will find successfully. However,
* all dependencies are on the real filesystem, so any require
* or import statements will fail. When that happens, our wrapper
* functions will then check fs for the requested file.
*/
const memFs = new MemoryFS();
const statOrig = memFs.stat.bind(memFs);
const readFileOrig = memFs.readFile.bind(memFs);
memFs.stat = function (_path, cb) {
statOrig(_path, function(err, result) {
if (err) {
return fs.stat(_path, cb);
} else {
return cb(err, result);
}
});
};
memFs.readFile = function (path, cb) {
readFileOrig(path, function (err, result) {
if (err) {
return fs.readFile(path, cb);
} else {
return cb(err, result);
}
});
};
export default function* compile(code) {
// Setup webpack
//create a directory structure in MemoryFS that matches
//the real filesystem
const rootDir = root.toString();
//write code snippet to memoryfs
const outputName = `file.js`;
const entry = path.join(rootDir, outputName);
const rootExists = memFs.existsSync(rootDir);
if (!rootExists) {
memFs.mkdirpSync(rootDir);
}
memFs.writeFileSync(entry, code);
//point webpack to memoryfs for the entry file
const compiler = webpack({
entry: entry,
output: {
filename: outputName
},
module: {
loaders: [
{ test: /\.json$/, loader: 'json' }
]
}
});
compiler.run = thenify(compiler.run);
//direct webpack to use memoryfs for file input
compiler.inputFileSystem = memFs;
compiler.resolvers.normal.fileSystem = memFs;
//direct webpack to output to memoryfs rather than to disk
compiler.outputFileSystem = memFs;
const stats = yield compiler.run();
//remove entry from memory. we're done with it
memFs.unlinkSync(entry);
const errors = stats.compilation.errors;
if (errors && errors.length > 0) {
//if there are errors, throw the first one
throw errors[0];
}
//retrieve the output of the compilation
const res = stats.compilation.assets[outputName].source();
return res;
}
Usage
var code = "var _ = require('underscore'); console.log(_);";
var bundle = yield compile(code); //is a valid js bundle containing the underscore source and a log statement logging _.
If there's not a better way, then I'll definitely encapsulate this into a subclass of MemoryFS, but I'm hoping there's a more sane way to accomplish this with Webpack's api.
Instead of memory-fs, the combination of unionfs/memfs/linkfs should help.
https://npmjs.com/unionfs
https://npmjs.com/memfs
https://npmjs.com/linkfs
I have created this snippet untested. I think you want the inputFS to be the real one and the output fs to be the in memory one. On the other hand you want all the dependencies of file.js to be constructed separately. For that I figured the webpack.optimize.CommonsChunkPlugin plugin could help. I expect webpack to write everything to the memory. I hope it works.
import webpack from 'webpack';
import MemoryFS from 'memory-fs';
import thenify from 'thenify';
import realFS from 'fs';
function* compile(code) {
const fs = new MemoryFS();
const compiler = webpack({
entry: {
file: '/file.js',
vendor: [
'underscore',
'other-package-name'
]
},
output: {
path: '/build',
filename: '[name].js'
},
module: {
loaders: [
{ test: /\.json$/, loader: 'json' }
],
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js')
]
});
compiler.run = thenify(compiler.run);
compiler.inputFileSystem = realFS;
compiler.resolvers.normal.fileSystem = fs; //this is needed for memfs
compiler.outputFileSystem = fs;
const stats = yield compiler.run();
//retrieve the output of the compilation
const res = stats.compilation.assets['file.js'].source();
return res;
}
You're using MemoryFS, which is a JavaScript reimplementation of a feature normally handled by the Operating System. I wonder, could you mount a directory using tmpfs at the Operating System level, then use that? webpack would then not know or care that the input file is actually stored in memory.
Assuming that you have mounted a memory-based filesystem at /media/memory, the webpack configuration code could be as simple as this:
resolve: {
root: ['/media/memory', ...other paths...],
},
output: {
path: '/wherever/you/want/the/output/files'
}
}
This approach also has a hidden benefit: If you want to debug the input code, you just mount /media/memory with a non-RAM-based filesystem and you can see what's being generated.
I know it's late but for the record here comes a code snippet.
import * as fs from 'fs';
import { resolve } from 'path';
import { Volume } from 'memfs';
import { ufs } from 'unionfs';
const volume = Volume.fromJSON({
[resolve(process.cwd(), 'test.js')]: 'this file is on memory not on disk'
});
ufs.use(fs).use(volume);
// Reads from memory
console.log(ufs.readFileSync(resolve(process.cwd(), 'test.js'), 'utf8'));
// Reads from disk
console.log(ufs.readFileSync(resolve(process.cwd(), 'package.json'), 'utf8'));
// Writing into memory
volume.writeFileSync(resolve(process.cwd(), 'test.memory'), 'This should be
on memory');
console.log(ufs.readFileSync(resolve(process.cwd(), 'test.memory'), 'utf8'));
// Writing into disk
ufs.writeFileSync(resolve(process.cwd(), 'test.disk'), 'This should be on disk');
console.log(ufs.readFileSync(resolve(process.cwd(), 'test.disk'), 'utf8'));
Hers the console output:
user1#pc playground % node inMem.mjs
this file is on memory not on disk
{
"name": "playground",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"memfs": "^3.3.0",
"unionfs": "^4.4.0"
}
}
This should be on memory
This should be on disk
user1#pc playground % ls .
inMem.mjs node_modules package.json yarn.lock

Webpack bundle validates but node server throwing syntax error

I am using webpack to bundle/transform jsx.
From the command line I'm running "webpack --watch". This creates my bundle without errors. Here's my webpack config and Application.js
'use strict';
var webpack = require('webpack');
module.exports = {
resolve: {
extensions: ['', '.js']
},
devtool: 'eval',
entry: './client.js',
output: {
path: __dirname+'/build/js',
filename: 'client.js'
},
module: {
loaders: [
{ test: /\.css$/, loader: 'style!css' },
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
}
};
var React = require('react'),
classSet = require('react/addons'),
Nav = require('./Nav.js'),
Home = require('./Home.js'),
Recipe = require('./Recipe.js'),
RecipeArchive = require('./RecipeArchive.js'),
About = require('./About.js'),
Timestamp = require('./Timestamp.js'),
RouterMixin = require('flux-router-component').RouterMixin;
var Application = React.createClass({
mixins: [RouterMixin],
getInitialState: function () {
this.store = this.props.context.getStore('ApplicationStore');
return this.store.getState();
},
componentDidMount: function () {
var self = this;
self._changeEventListener = function () {
var state = self.store.getState();
self.setState(state);
};
self.store.on('change', self._changeEventListener);
},
componentWillUnmount: function () {
var self = this;
self.store.removeListener('change', self._changeEventListener);
self._changeEventListener = null;
},
render: function () {
return (
<div>test</div>
);
}
});
module.exports = Application;
Then I'm running my node server which throws an error.
node server.js
results in:
/Users//Documents/routing/components/Application.js:39
<div>test</div>
^
SyntaxError: Unexpected token <
How should I be running my project to allow me to include jsx/harmony in my .js files?
UPDATE: Solution
As Brandon pointed out, I needed to use node-jsx for transforms in Node. At the top I've of my server.js file I've added and alls working:
require('node-jsx').install({ extension: '.js', harmony: true });
Webpack just creates a client-side bundle; when you run your Node app, it doesn't use webpack to load the files. Prevously you could use something like node-jsx to require JSX files in Node.js, but that project has since been deprecated in favor of Babel. In particular, you can use babel-register to require JSX files in Node.

Categories

Resources