compile typescript to js with webpack and keep directory structure - node.js

I want webpack to compile my typescript node project into js but I want it to maintain the directory structure and not bundle into 1 file.
Is this possible?
My structure is:
src
|_controllers
|_home
|_index.ts
|_ services
// etc.
And I want it to compile to:
dist
|_controllers
|_home
|_index.ts
|_ services
// etc.
currently my config is like this:
{
name: 'api',
target: 'node',
externals: getExternals(),
entry: isDevelopment ? [...entries] : entries,
devtool: !isDevelopment && 'cheap-module-source-map',
output: {
path: paths.appBuild,
filename: '[name].js',
libraryTarget: 'commonjs2'
},
plugins: [
new WriteFilePlugin(),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
}),
isProduction && new webpack.optimize.ModuleConcatenationPlugin()
]
}
Is it possible with webpack?
I can't use just tsc because I have a yarn workspaces monorepo and I might have a link reference like this:
import {something} from '#my/package';
#my/package does not exist in npm and only exists in the context of the monorepo, I can use node externals with webpack to include it in the bundle I don't think I can keep the folder structure this way.
Would the new typescript 3.0 project references solve this problem?

Understood your concerns. Looks it's completely doable by transpile-webpack-plugin. You may setup it as below:
const TranspilePlugin = require('transpile-webpack-plugin');
module.exports = {
// ...
entry: './src/controllers/home/index.ts',
output: {
path: __dirname + '/dist',
},
plugins: [new TranspilePlugin({ longestCommonDir: './src' })],
};
The plugin works well with webpack resolve.alias and externals. All the files directly or indirectly imported by the entry will be collected and compiled into the output dir seperately without bundling but with keeping the directory structure and file names. Just have a try. ๐Ÿ™‚

Related

How to: 1 Webpack for all project with importing lib from node_modules

// Project Tree View: My idea about using Webpack.
+ Toolkit:
- D:/Toolkit/Webpack/webpack.config.js
+ Project:
- D:/Project/A/build/index.ts
- D:/Project/B/build/index.ts
- D:/Project/C/build/index.ts
// Toolkit: [webpack.config.js] file
const path = require('path');
module.exports = (env) => {
let project_root = env.path;
console.log(env);
console.log(__dirname);
return {
mode: env.mode,
entry: project_root+'/build/index.ts',
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
include: [
path.resolve(project_root, 'build'),
path.resolve('./node_modules'),
]
}, {
test: /\.scss$/,
use: [
"style-loader",
"css-loader",
"sass-loader",
]
}
]
},
output: {
publicPath: 'public',
filename: 'script.js',
path: path.resolve(project_root, 'assets/js')
},
resolve: {
modules: ['node_modules'],
},
}
}
Build command: yarn build --env=path=D:/Project/C
The command works without error, but when importing any library from the node_modules
Ex:
import {lib} from "example_lib";
import {lib} from "~example_lib";
import {lib} from "#example_lib";
import {lib} from "node_modules/example_lib";
import {lib} from "./node_modules/example_lib";
The Error
ERROR in ../A/build/index.ts 1:0-52
Module not found: Error: Can't resolve 'example_lib' in 'D:\Project\A'
resolve 'example_lib' in 'D:\Project\A'
Parsed request is a module
No description file found in D:\Project\A\build or above
resolve as module
D:\Project\A\build\node_modules doesn't exist or is not a directory
...
D:\node_modules doesn't exist or is not a directory
ERROR in D:\Project\A\build\index.ts
../A/build/index.ts 1:20-49
[tsl] ERROR in D:\Project\A\build\index.ts(1,21)
TS2792: Cannot find module 'example_lib'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?
webpack 5.26.2 compiled with 2 errors in 1939 ms
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
The Question is:
How to use ONLY 1 Webpack node_modules for all projects ?
Gulp can resolve this issue but how about Webpack ?
I do not want every project to have 1 node_modules or webpack, package,... inside (it trash my PC !)
The issue has been resolved !
The above code is correct.
Format ".js" works normally; however ".ts" need to add // #ts-ignore above every import external node_modules.
This is the answer !
// #ts-ignore
import {lib} from "example_lib";
NOTE: 7 days researching Webpack
This is my opinions:
Gulp is much more than Webpack even it has lower user
These are the Pros about Gulp that Webpack should have !
Less time for controlling the core.
User-friendly.
Clear and Clean Structure, less bracket.
Easy and Flexible for creating the custom config.
Easy to use, develop through time.
Not just for web field, on the backup data, auto,... as well.
1 node_modules for all external projects (Gulp less bug and coding line than Webpack).
Compile time are the same.
๐Ÿ‘‰๐Ÿพ Gulp is the King ๐Ÿ‘‘ !

Unable to implement webpack in project with node-red

I am trying to implement webpack in my project which contains node-red. However, I keep getting the following warning. Please suggest how to solve this error -
WARNING in ./node_modules/node-red/red/runtime/storage/localfilesystem/projects/git/node-red-ask-pass.sh 1:26
Module parse failed: Unexpected token (1:26)
You may need an appropriate loader to handle this file type.
> "$NODE_RED_GIT_NODE_PATH" "$NODE_RED_GIT_ASKPASS_PATH" "$NODE_RED_GIT_SOCK_PATH" $#
|
# ./node_modules/node-red/red/runtime/storage sync ^\.\/.*$ ./localfilesystem/projects/git/node-red-ask-pass.sh
# ./node_modules/node-red/red/runtime/storage/index.js
# ./node_modules/node-red/red/runtime/index.js
# ./app.js
My webpack.config.js is -
const path = require('path');
var nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
externals: [nodeExternals()],
entry: './app.js',
output: {
path: path.resolve(__dirname, './output'),
filename: 'bundle.js'
},
resolve: {
extensions: ['.js','.json', '.sh'],
modules: [
'node_modules'
],
},
module: {
rules: [
{
test:/\.css$/,
use:['style-loader','css-loader']
},
{
test: /\.coffee$/,
use: [ 'coffee-loader' ]
}
]
}
};
For Webpack, every file is a .js. In order to handle other extensions, like .css or .sh, you're supposed to use a loader, like you did with css-loader, that will tranform CSS rules into JS.
The issue you're facing is that you've got an import chain (./app.js -> .../index.js -> .../index.js -> .../node-red-ask-pass.sh), so Webpack will, at some point, will import a .sh file, but will throw an error because shell code is obviousouly invalid JavaScript. that is why you're seeing the error that you have.
By the way, I couldn't reproduce the issue you're facing:
npm init -y
npm i node-red
# ./node_modules/node-red/red is not a directory
So it was probably a node-red bug. Update the package to the latest version.

Using Environment Variables with Vue.js

I've been reading the official docs and I'm unable to find anything on environment variables. Apparently there are some community projects that support environment variables but this might be overkill for me. So I was wondering if there's something simple out of the box that works natively when working on a project already created with Vue CLI.
For example, I can see that if I do the following the right environment prints out meaning this is already setup?
mounted() {
console.log(process.env.ROOT_API)
}
I'm a kinda new to env variables and Node.
FYI using Vue CLI version 3.0 beta.
Vue.js with Webpack
If you use vue cli with the Webpack template (default config), you can create and add your environment variables to a .env file.
The variables will automatically be accessible under process.env.variableName in your project. Loaded variables are also available to all vue-cli-service commands, plugins and dependencies.
You have a few options, this is from the Environment Variables and Modes documentation:
.env # loaded in all cases
.env.local # loaded in all cases, ignored by git
.env.[mode] # only loaded in specified mode
.env.[mode].local # only loaded in specified mode, ignored by git
Your .env file should look like this:
VUE_APP_MY_ENV_VARIABLE=value
VUE_APP_ANOTHER_VARIABLE=value
As noted in comment below:
If you are using Vue cli 3, only variables that start with VUE_APP_ will be loaded.
Don't forget to restart serve if it is currently running.
Vue.js with Vite
Vite exposes env variables that start with VITE_ on the special import.meta.env object.
Your .env should look like this:
VITE_API_ENDPOINT=value
VITE_API_KEY=value
These variables can be accessed in Vue.js components or JavaScript files under import.meta.env.VITE_API_ENDPOINT and import.meta.env.VITE_API_KEY.
Tip: Remember to restart your development server whenever you change or add a variable in the .env file if it's running.
For more info, please see the Vite documentation for env variables.
If you are using Vue cli 3, only variables that start with VUE_APP_ will be loaded.
In the root create a .env file with:
VUE_APP_ENV_VARIABLE=value
And, if it's running, you need to restart serve so that the new env vars can be loaded.
With this, you will be able to use process.env.VUE_APP_ENV_VARIABLE in your project (.js and .vue files).
Update
According to #ali6p, with Vue Cli 3, isn't necessary to install dotenv dependency.
Create two files in root folder (near by package.json) .env and .env.production
Add variables to theese files with prefix VUE_APP_ eg: VUE_APP_WHATEVERYOUWANT
serve uses .env and build uses .env.production
In your components (vue or js), use process.env.VUE_APP_WHATEVERYOUWANT to call value
Don't forget to restart serve if it is currently running
Clear browser cache
Be sure you are using vue-cli version 3 or above
For more information: https://cli.vuejs.org/guide/mode-and-env.html
In the root of your project create your environment files:
.env
.env.someEnvironment1
.env.SomeEnvironment2
To then load those configs, you would specify the environment via mode i.e.
npm run serve --mode development //default mode
npm run serve --mode someEnvironment1
In your env files you simply declare the config as key-value pairs, but if you're using vue 3, you must prefix with VUE_APP_:
In your .env:
VUE_APP_TITLE=This will get overwritten if more specific available
.env.someEnvironment1:
VUE_APP_TITLE=My App (someEnvironment1)
You can then use this in any of your components via:
myComponent.vue:
<template>
<div>
{{title}}
</div>
</template>
<script>
export default {
name: "MyComponent",
data() {
return {
title: process.env.VUE_APP_TITLE
};
}
};
</script>
Now if you ran the app without a mode it will show the 'This will get...' but if you specify a someEnvironment1 as your mode then you will get the title from there.
You can create configs that are 'hidden' from git by appending .local to your file: .env.someEnvironment1.local - very useful for when you have secrets.
Read the docs for more info.
A problem I was running into was that I was using the webpack-simple install for VueJS which didn't seem to include an Environment variable config folder. So I wasn't able to edit the env.test,development, and production.js config files. Creating them didn't help either.
Other answers weren't detailed enough for me, so I just "fiddled" with webpack.config.js. And the following worked just fine.
So to get Environment Variables to work, the webpack.config.js should have the following at the bottom:
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
Based on the above, in production, you would be able to get the NODE_ENV variable
mounted() {
console.log(process.env.NODE_ENV)
}
Now there may be better ways to do this, but if you want to use Environment Variables in Development you would do something like the following:
if (process.env.NODE_ENV === 'development') {
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"'
}
})
]);
}
Now if you want to add other variables with would be as simple as:
if (process.env.NODE_ENV === 'development') {
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
ENDPOINT: '"http://localhost:3000"',
FOO: "'BAR'"
}
})
]);
}
I should also note that you seem to need the "''" double quotes for some reason.
So, in Development, I can now access these Environment Variables:
mounted() {
console.log(process.env.ENDPOINT)
console.log(process.env.FOO)
}
Here is the whole webpack.config.js just for some context:
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
}, {
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
if (process.env.NODE_ENV === 'development') {
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
ENDPOINT: '"http://localhost:3000"',
FOO: "'BAR'"
}
})
]);
}
This is how I edited my vue.config.js so that I could expose NODE_ENV to the frontend (I'm using Vue-CLI):
vue.config.js
const webpack = require('webpack');
// options: https://github.com/vuejs/vue-cli/blob/dev/docs/config.md
module.exports = {
// default baseUrl of '/' won't resolve properly when app js is being served from non-root location
baseUrl: './',
outputDir: 'dist',
configureWebpack: {
plugins: [
new webpack.DefinePlugin({
// allow access to process.env from within the vue app
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
}
})
]
}
};
In vue-cli version 3:
There are the three options for .env files:
Either you can use .env or:
.env.test
.env.development
.env.production
You can use custom .env variables by using the prefix regex as /^/ instead of /^VUE_APP_/ in /node_modules/#vue/cli-service/lib/util/resolveClientEnv.js:prefixRE
This is certainly not recommended for the sake of developing an open source app in different modes like test, development, and production of .env files. Because every time you npm install .. , it will be overridden.
In addition to the previous answers, if you're looking to access VUE_APP_* env variables in your sass (either the sass section of a vue component or a scss file), then you can add the following to your vue.config.js (which you may need to create if you don't have one):
let sav = "";
for (let e in process.env) {
if (/VUE_APP_/i.test(e)) {
sav += `$${e}: "${process.env[e]}";`;
}
}
module.exports = {
css: {
loaderOptions: {
sass: {
data: sav,
},
},
},
}
The string sav seems to be prepended to every sass file that before processing, which is fine for variables. You could also import mixins at this stage to make them available for the sass section of each vue component.
You can then use these variables in your sass section of a vue file:
<style lang="scss">
.MyDiv {
margin: 1em 0 0 0;
background-image: url($VUE_APP_CDN+"/MyImg.png");
}
</style>
or in a .scss file:
.MyDiv {
margin: 1em 0 0 0;
background-image: url($VUE_APP_CDN+"/MyImg.png");
}
from https://www.matt-helps.com/post/expose-env-variables-vue-cli-sass/
Important (in Vue 4 and likely Vue 3+ as well!): I set VUE_APP_VAR but could NOT see it by console logging process and opening the env object. I could see it by logging or referencing process.env.VUE_APP_VAR. I'm not sure why this is but be aware that you have to access the variable directly!
For those using Vue CLI 3 and the webpack-simple install, Aaron's answer did work for me however I wasn't keen on adding my environment variables to my webpack.config.js as I wanted to commit it to GitHub. Instead I installed the dotenv-webpack plugin and this appears to load environment variables fine from a .env file at the root of the project without the need to prepend VUE_APP_ to the environment variables.
I am having same problem in vuecli#5. Trying to solve by reading official doc but can't get proper solution. After long time i got solution and it works fine.
Create .env file on root dir. touch .env
Set value on it i.e APP_NAME=name
vue.config.js file past it process.env.VUE_APP_VERSION = require('./package.json').version
Log to any method console.log(process.env.APP_NAME);
Running multiple builds with different .env files ๐Ÿญ
In my app I wanted to have multiple production builds, one for a web app, and another for a browser extension.
In my experience, changing build modes can have side effects as other parts of the build process can rely on being in production for example, so here's another way to provide a custom env file (based on #GrayedFox's answer):
package.json
{
"scripts": {
"build": "vue-cli-service build",
"build:custom": "VUE_CLI_SERVICE_CONFIG_PATH=$PWD/vue.config.custom.js vue-cli-service build",
}
}
vue.config.custom.js
// install `dotenv` with `yarn add -D dotenv`
const webpack = require("webpack");
require("dotenv").config({ override: true, path: "./.env.custom" });
module.exports = {
plugins: [new webpack.EnvironmentPlugin({ ...process.env })],
};
Note 1: VUE_CLI_SERVICE_CONFIG_PATH swaps out the config from the default of vue.config.js, so any settings set in there will not apply for the custom build.
Note 2: this will load .env.production before .env.custom, so if you don't want any of the environment variables set in .env.production in your custom build, you'll want to set those to a blank string in .env.custom.
Note 3: If you don't set override: true then environment variables in .env.production will take precedence over .env.custom.
Note 4: If you are looking to have multiple different builds using vue-cli, the --skip-plugins option is very useful.
**just install this **
npm install -g #vue/cli
at your project
it is worked with me

My tsconfig.json cannot find a module in my node_modules directory, not sure what is wrong

I have the following hierarchy:
dist/
|- BuildTasks/
|- CustomTask/
- CustomTask.js
node_modules/
source/
|- BuildTasks/
|- CustomTask/
- CustomTask.ts
- tsconfig.json
Additionally, I am trying to create a VSTS Task extension for internal (private) usage. Originally, I had my tsconfig.json at my root directory, and everything worked just fine on my local machine. The problem is that a VSTS Extension requires all the files to be included in the same directory as the task folder itself. See https://github.com/Microsoft/vsts-task-lib/issues/274 for more information:
you need to publish a self contained task folder. the agent doesnt run
npm install to restore your dependencies.
Originally, I had a this problem solved by include a step to copy the entire node_modules directory into each Task folder, in this case my CustomTask folder which contains my JS file. But, this seems a bit much considering that not every task I am writing has the same module requirements.
My idea was to create a tsconfig.json in each of the Task folders which would specify to create a single output file containing all of the dependent modules, but unfortunately it is not working:
{
"compilerOptions": {
"baseUrl": ".",
"target": "ES6",
"module": "system",
"strict": true,
"rootDir": ".",
"outFile": "../../../dist/BuildTasks/CustomTask/CustomTask.js",
"paths": {
"*" : ["../../../node_modules/*"]
}
}
}
Prior to adding the "paths", I was getting the following errors:
error TS2307: Cannot find module 'vsts-task-lib/task'.
error TS2307: Cannot find module 'moment'.
After adding the paths, I still get the error that it cannot find the module 'moment', which is in my node_modules directory. Also, when I look at the output JS it seems that it didn't include the 'vsts-tasks-lib' code necessary, maybe because it still had an error in regards to the 'moment' module? Not sure what I missed?
Using webpack to compile JavaScript modules, simple sample:
webpack.config.js:
const path = require('path');
module.exports = {
entry: './testtask.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
node: {
fs: 'empty'
},
target: 'node'
};
After that, there are just bundle.js and task.json in task folder.
Update๏ผš sample code in testtask.ts:
import tl = require('vsts-task-lib/task');
import fs = require('fs');
console.log('Set variable================');
tl.setVariable('varCode1', 'code1');
tl.setTaskVariable('varTaskCode1', 'taskCode1');
var taskVariables = tl.getVariables();
console.log("variables are:");
for (var taskVariable of taskVariables) {
console.log(taskVariable.name);
console.log(taskVariable.value);
}
console.log('##vso[task.setvariable variable=LogCode1;]LogCode1');
console.log('end========================');
console.log('current path is:' + __dirname);
fs.appendFile('TextFile1.txt', 'data to append', function (err) {
if (err) throw err;
console.log('Saved!');
});
console.log('configure file path:' + process.env.myconfig);
console.log('configure file path2:' + process.env.myconfig2);

webpack multiple output path, limit public access, or custom index.html?

I am using node-express, with typescript.
my folder is setup as follows:
.dist
public
public.js
index.html
server.js
node_modules
src
classes
namespace1
module1
public
app - all angular files.
main.ts
routes
index.ts
app.ts
package.json
tsconfig.json
webpack.config.js
Now, I need webpack to output 2 files to /public/public.js and /server.js at .dist folder. nodejs will then run from .dist/server.js, and I want to separate public.js to prevent client to access server.js
I also use html-webpack-plugin to generate html files.
I have tried using a little hack like
entry: {
"server": "./src/app.ts",
"public/public": "./src/public/main.ts"
}
but then html-webpack-plugin made index.html to load script from /public/public.js instead of public.js
Now, I think we can solve this in 3 way.
Let server.js send public.js using http://localhost/public.js, but it will make managing static folder a little bit complicated. but I will think some way to trick it. Question: how to serve public.js via server.js?
Set entry to "public": "./src/public/main.ts". Question: how to put that public.js into public folder?
Setup html-webpack-plugin to load from /public.js instead of /public/public.js and make index.html inside /public folder. As of now, html-webpack-plugin generates <script type="text/javascript" src="../public/polyfill.js"></script><script type="text/javascript" src="../public/public.js"></script></body> where is should make <script type="text/javascript" src="/polyfill.js"></script><script type="text/javascript" src="/public.js"></script></body>
Question: How to do that?
Or is there any other idea to solve this? I am open to any suggestion.
Thank you
I think I can answer scenarios 2 and 3.
2- Apart of setting up entry points, you can set up some output configuration. http://webpack.github.io/docs/configuration.html#output
3- Also you could use copy webpack plugin to copy the files you need into your public folder.
https://github.com/kevlened/copy-webpack-plugin
I do it in one of my projects, this is the code that I add on the webpack config file:
new CopyWebpackPlugin([
{from: __dirname + '/src/public'}
])
Hope this helps.
Regards.
I managed by using this config.
module.exports = [
{
entry: "./src/app.ts",
output: {
filename: "server.js",
path: __dirname + "/dist"
},
target: "node",
resolve: {
extensions: ['.ts', '.js', '.tsx', '.jsx']
},
node: {
__dirname: false
},
module: { // all modules here for server
}
}, {
entry: "./src/public/main.ts",
output: {
filename: "bundle.js",
path: __dirname + "/dist/public"
},
target: "web",
plugins: [
new htmlPlugin({
filename: 'index.html'
})
],
resolve: {
extensions: ['.ts', '.js', '.tsx', '.jsx']
},
module: { // all your modules here.
}
}
]

Resources