This is my project structure:
node_modules
dist
config
- webpack.common.js
- webpack.dev.js
- webpack.prod.js
- webpack.test.js
src
- app
- app-routing.module.ts
- app.component.html
- app.component.scss
- app.component.ts
- app.module.ts
- index.ts
- index.html
- main.browser.ts
- polyfills.browser.ts
angular.json
package.json
postcss.config.js
tsconfig.json
tsconfig.webpack.json
webpack.config.js
When I try to compile or build the project with yarn it's failing because it doesn't find a scss file in the node_modules:
In fact, the scss file exits:
The file belongs to angular flex layout
Below is the app.components.scss
that is trying to use layout-bp
who belongs to @angular/flex-layout/mq
@import '@angular/flex-layout/mq';
.top-bar {
height: 25px;
width: 100%;
}
.start-over {
font-weight: bold;
border-radius: 2.5px;
.material-icons {
margin-right: 5px;
transform: scalex(-1);
}
span {
font-size: 13px;
vertical-align: middle;
}
&--mobile {
display: none;
.material-icons {
transform: scalex(-1) translate(12px, -12px);
}
}
}
@media screen {
@each $br in ('xs', 'sm') {
@include layout-bp($br) {
.start-over {
margin-right: 8px;
}
}
}
}
I think the issue is because it's trying to find the mq.scss file inside src/app/
when the file is in node_modules
To run the project I use yarn start:consumer
or yarn start
This command does the following:
1) Build the project.
2) Run the files in the dist
folder with: node dist/app.js
Below I copy the postcss.config.js
, webpack.config.js
, webpack.common.js
, webpack.dev.js
and package.json
.
postcss.config.js
module.exports = {
parser: 'postcss-scss',
plugins: {
"postcss-import": {},
'postcss-each': {},
'postcss-at-rules-variables': {},
'postcss-simple-vars': {},
'precss': {},
'postcss-functions': {},
'cssnano': {},
autoprefixer: {
browsers: ['last 2 versions']
},
}
};
webpack.config.js
switch (process.env.NODE_ENV) {
case 'prod':
case 'production':
module.exports = require('./config/webpack.prod')({env: 'production'});
break;
case 'test':
case 'testing':
module.exports = require('./config/webpack.test')({env: 'test'});
break;
case 'dev':
case 'development':
default:
module.exports = require('./config/webpack.dev')({env: 'development'});
}
webpack.common.js
const helpers = require('./helpers');
/*
* Webpack Plugins
*/
const DefinePlugin = require('webpack/lib/DefinePlugin');
const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const buildUtils = require('./build-utils');
/*
* Webpack configuration
*/
module.exports = function (options) {
const METADATA = Object.assign({}, buildUtils.DEFAULT_METADATA, options.metadata || {});
const supportES2015 = buildUtils.supportES2015(METADATA.tsConfigPath);
const entry = {
'polyfills': './src/polyfills.browser.ts',
'main': './src/main.browser.ts'
};
return {
entry: entry,
resolve: {
mainFields: [ ...(supportES2015 ? ['es2015'] : []), 'browser', 'module', 'main' ],
extensions: ['.ts', '.js', '.json', '.css', '.scss'],
modules: [helpers.root('src'), helpers.root('node_modules')],
alias: buildUtils.rxjsAlias(supportES2015)
},
module: {
rules: [
{
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
loader: '@ngtools/webpack'
},
{
test: /\.css$/,
use: ['to-string-loader', 'css-loader'],
exclude: [helpers.root('src', 'styles')]
},
{
test: /\.component\.(sass|scss)$/,
exclude: [helpers.root('src', 'styles')],
use: [
'to-string-loader',
{ loader: 'css-loader', options: {importLoaders: 1, modules: true } },
'postcss-loader'
]
},
{
test: /\.html$/,
use: 'raw-loader',
exclude: [helpers.root('src/index.html')]
},
{
test: /\.(jpg|png|gif|svg)$/,
loader: 'file-loader',
options: {
name: 'assets/[name].[hash].[ext]',
}
},
{
test: /\.(eot|woff2?|svg|ttf)([\?]?.*)$/,
use: 'file-loader'
}
],
},
plugins: [
new DefinePlugin({
'ENV': JSON.stringify(METADATA.ENV),
'process.env.ENV': JSON.stringify(METADATA.ENV),
'process.env.NODE_ENV': JSON.stringify(METADATA.ENV),
}),
new HtmlWebpackPlugin({
chunksSortMode: 'none'
}),
new ScriptExtHtmlWebpackPlugin({
sync: /inline|polyfills|vendor/,
defaultAttribute: 'async',
preload: [/polyfills|vendor|main/],
prefetch: [/chunk/]
}),
new LoaderOptionsPlugin({}),
],
node: {
global: true,
crypto: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
};
};
webpack.dev.js
const helpers = require('./helpers');
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
const commonConfig = require('./webpack.common.js'); // the settings that are common to prod and dev
const webpack = require('webpack');
/**
* Webpack Plugins
*/
const DefinePlugin = require('webpack/lib/DefinePlugin');
const NamedModulesPlugin = require('webpack/lib/NamedModulesPlugin');
const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const buildUtils = require('./build-utils');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ngToolsWebpack = require('@ngtools/webpack');
/**
* Webpack configuration
*
* See: http://webpack.github.io/docs/configuration.html#cli
*/
module.exports = function (options) {
/* HERE ARE DEFINED THE CONST USED BELOW. I REMOVED THEM BECAUSE THEY CONTAIN SENSITIVE INFORMATION OF THE PROJECT */
const METADATA = Object.assign({}, buildUtils.DEFAULT_METADATA, {
host: HOST,
port: PORT,
ENV: ENV,
STYLES_URL: 'http://' + HOST + ':' + PORT + '/styles',
HMR: helpers.hasProcessFlag('hot'),
PUBLIC: process.env.PUBLIC_DEV || HOST + ':' + PORT,
API_BASE_URL: API_BASE_URL,
GOOGLE_MAPS_API_KEY: GOOGLE_MAPS_API_KEY,
API_KEY: API_KEY,
THUMBOR_BASE_URL: THUMBOR_BASE_URL,
STRUCTURE: STRUCTURE,
WIDGET_BASE_URL: WIDGET_BASE_URL,
WEBPAGE_BASE_URL: WEBPAGE_BASE_URL,
SOCKETS_URL: SOCKETS_URL,
AUTH_BASE_URL: AUTH_BASE_URL,
FIREBASE_API_KEY: FIREBASE_API_KEY,
FIREBASE_APP_ID: FIREBASE_APP_ID,
FIREBASE_MEASUREMENT_ID: FIREBASE_MEASUREMENT_ID
});
return webpackMerge(commonConfig({env: ENV, metadata: METADATA}), {
mode: 'none',
devtool: 'cheap-module-source-map',
output: {
path: '/',
publicPath: '/',
filename: '[name].bundle.js',
sourceMapFilename: '[file].map',
chunkFilename: '[id].chunk.js',
library: 'ac_[name]',
libraryTarget: 'var',
},
plugins: [
new LoaderOptionsPlugin({
debug: true,
options: {}
}),
new DefinePlugin({
'ENV': JSON.stringify(METADATA.ENV),
'API_BASE_URL': JSON.stringify(API_BASE_URL),
'GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY),
'API_KEY': JSON.stringify(API_KEY),
'THUMBOR_BASE_URL': JSON.stringify(THUMBOR_BASE_URL),
'STRUCTURE': JSON.stringify(STRUCTURE),
'WIDGET_BASE_URL': JSON.stringify(WIDGET_BASE_URL),
'WEBPAGE_BASE_URL': JSON.stringify(WEBPAGE_BASE_URL),
'SOCKETS_URL': JSON.stringify(SOCKETS_URL),
'AUTH_BASE_URL': JSON.stringify(AUTH_BASE_URL),
'FIREBASE_API_KEY': JSON.stringify(FIREBASE_API_KEY),
'FIREBASE_APP_ID': JSON.stringify(FIREBASE_APP_ID),
'FIREBASE_MEASUREMENT_ID': JSON.stringify(FIREBASE_MEASUREMENT_ID),
'process.env': {
'ENV': JSON.stringify(METADATA.ENV),
'NODE_ENV': JSON.stringify(METADATA.ENV),
'API_BASE_URL': JSON.stringify(API_BASE_URL),
'API_KEY': JSON.stringify(API_KEY),
'THUMBOR_BASE_URL': JSON.stringify(THUMBOR_BASE_URL),
'STRUCTURE': JSON.stringify(STRUCTURE),
'WIDGET_BASE_URL': JSON.stringify(WIDGET_BASE_URL),
'WEBPAGE_BASE_URL': JSON.stringify(WEBPAGE_BASE_URL),
'SOCKETS_URL': JSON.stringify(SOCKETS_URL),
'AUTH_BASE_URL': JSON.stringify(AUTH_BASE_URL),
'FIREBASE_API_KEY': JSON.stringify(FIREBASE_API_KEY),
'FIREBASE_APP_ID': JSON.stringify(FIREBASE_APP_ID),
'FIREBASE_MEASUREMENT_ID': JSON.stringify(FIREBASE_MEASUREMENT_ID)
}
}),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: 'src/index.html',
title: METADATA.title,
chunksSortMode: function (a, b) {
const entryPoints = ["inline", "polyfills", "sw-register", "styles", "vendor", "main"];
return entryPoints.indexOf(a.names[0]) - entryPoints.indexOf(b.names[0]);
},
metadata: METADATA,
inject: 'body'
}),
new CopyWebpackPlugin([
{from: 'src/assets', to: 'assets'},
{from: 'src/styles/loading.css', to: ''}
]
),
new ngToolsWebpack.AngularCompilerPlugin({
tsConfigPath: helpers.root('tsconfig.json'),
mainPath: helpers.root('src/main.browser.ts'),
sourceMap: true,
})
],
devServer: {
port: METADATA.port,
host: METADATA.host,
hot: METADATA.HMR,
compress: true,
public: METADATA.PUBLIC,
historyApiFallback: true,
disableHostCheck: true,
watchOptions: {
// if you're using Docker you may need this
// aggregateTimeout: 300,
// poll: 1000,
ignored: /node_modules/
},
overlay: {
errors: true,
warning: false
},
setup: function (app) {
// For example, to define custom handlers for some paths:
// app.get('/some/path', function(req, res) {
// res.json({ custom: 'response' });
// });
}
},
node: {
global: true,
crypto: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
});
}
package.json
{
"name": "my-project",
"version": "1.26.0",
"description": "An angular project",
"keywords": [
"angular",
"angular2",
"angular5",
"webpack",
"typescript"
],
"author": "My Self",
"homepage": "",
"license": "MIT",
"sideEffects": [
"src/polyfills.browser.ts"
],
"scripts": {
"build:aot:prod": "yarn clean:public && cross-env yarn webpack --config config/webpack.prod.js --progress --profile --bail",
"build": "yarn clean:public && yarn clean:dist && yarn server:build:prod && yarn build:aot:prod && yarn minify:externalcss",
"clean:public": "yarn rimraf ./public/**.js ./public/**.css** ./public/**.html ./public/assets ./public/**.json",
"clean:dist": "yarn rimraf dist",
"clean:install": "yarn set progress=false && yarn install",
"clean": "yarn cache clean --force && yarn rimraf node_modules doc coverage dist ./public/**.js ./public/**.css** ./public/**.html ./public/assets",
"node": "node",
"rimraf": "rimraf",
"server:build": "tsc -p ./server",
"server:dev": "yarn server:build && cross-env NODE_ENV=development tsc-watch -p ./server --outDir ./dist --onSuccess \"yarn server\"",
"server:dev:consumer": "yarn server:build && cross-env NODE_ENV=development tsc-watch -p ./server --outDir ./dist --onSuccess \"yarn server\"",
"server": "node dist/app.js",
"start": "yarn server:dev",
"start:vm": "yarn server:dev:vm",
"start:consumer": "yarn server:dev:consumer",
"tslint": "tslint",
"typedoc": "typedoc",
"watch:test": "yarn test --auto-watch --no-single-run",
"webdriver:update:stock": "./node_modules/protractor/bin/webdriver-manager update --chrome false --gecko false --standalone false",
"webdriver:update:new": "yarn webdriver:update:stock && ./node_modules/webdriver-manager/bin/webdriver-manager update --gecko false --standalone false && yarn webdriver:move && yarn webdriver:update:replacement",
"webdriver:move": "mv ./node_modules/webdriver-manager/selenium/update-config.json ./node_modules/protractor/node_modules/webdriver-manager/selenium/",
"webpack-dev-server": "node --max_old_space_size=4096 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
"webpack:bundle:analyzer": "webpack-bundle-analyzer",
"webpack": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js",
"minify": "minify",
"minify:externalcss": "yarn minify --output ./public/loading.css ./src/styles/loading.css",
"snyk-protect": "snyk protect",
"prepublish": "npm run snyk-protect",
"precommit": "yarn test"
},
"dependencies": {
"@agm/core": "^1.0.0-beta.2",
"@angular-mdl/core": "git+ssh://git@github.com/mtuduri/angular2-mdl.git#v6.0.0",
"@angular/animations": "7.2.0",
"@angular/cdk": "^7.2.2",
"@angular/common": "7.2.0",
"@angular/compiler": "7.2.0",
"@angular/core": "7.2.0",
"@angular/fire": "^5.2.1",
"@angular/flex-layout": "^7.0.0-beta.23",
"@angular/forms": "7.2.0",
"@angular/http": "7.2.0",
"@angular/material": "^7.2.2",
"@angular/platform-browser": "7.2.0",
"@angular/platform-browser-dynamic": "7.2.0",
"@angular/platform-server": "7.2.0",
"@angular/router": "7.2.0",
"@angularclass/hmr": "~2.1.3",
"@angularclass/hmr-loader": "^3.0.4",
"@ngxs/logger-plugin": "^3.4.3",
"@ngxs/storage-plugin": "^3.4.3",
"@ngxs/store": "^3.4.3",
"angular2-notifications": "^1.0.2",
"angular2-text-mask": "^8.0.4",
"bluebird": "3.5.1",
"classlist.js": "^1.1.20150312",
"config": "1.29.4",
"core-js": "^2.5.5",
"express": "4.16.2",
"express-healthcheck": "^0.1.0",
"express-winston": "2.4.0",
"firebase": "^7.2.0",
"hammerjs": "^2.0.8",
"helmet": "3.10.0",
"honeybadger": "1.2.1",
"honeybadger-js": "^0.5.2",
"ie-shim": "^0.1.0",
"install": "^0.13.0",
"jwt-decode": "^2.2.0",
"material-design-lite": "^1.3.0",
"moment": "^2.24.0",
"newrelic": "^4.9.0",
"ng2-odometer": "^1.1.3",
"ngx-loadable": "^1.0.10",
"ngx-mask": "^8.0.1",
"ngx-take-until-destroy": "^5.4.0",
"postcss": "7.0.17",
"postcss-at-rules-variables": "^0.1.10",
"postcss-custom-properties": "^9.0.2",
"postcss-each": "^0.10.0",
"postcss-easy-import": "^3.0.0",
"postcss-import-resolver": "^2.0.0",
"postcss-load-config": "^2.1.0",
"postcss-mixins": "^6.2.3",
"postcss-modules-tilda": "^1.0.1",
"postcss-node-modules-replacer": "^0.0.1",
"postcss-plugin": "^1.0.0",
"postcss-prefix-selector": "1.7.2",
"postcss-sass": "^0.4.2",
"postcss-simple-vars": "^5.0.2",
"postcss-url": "^8.0.0",
"precss": "^4.0.0",
"reflect-metadata": "^0.1.12",
"request": "^2.83.0",
"request-promise": "^4.2.2",
"rxjs": "^6.3.2",
"rxjs-compat": "^6.3.2",
"sass-lazy-compiler": "git+ssh://git@github.com/theappraisallane/sass-lazy-compiler.git#1.1.1",
"semver": "^5.6.0",
"snyk": "^1.163.1",
"stylelint": "^12.0.1",
"tal-ng-dcp": "git+ssh://git@github.com/theappraisallane/tal-ng-dcp.git#^1.4.64",
"tal-ng-fileupload": "git+ssh://git@github.com/theappraisallane/tal-ng-fileupload.git#^1.0.5",
"tal-ng-sockets": "git+ssh://git@github.com/theappraisallane/tal-ng-sockets.git#0.0.22",
"tal-ng-translate": "git+ssh://git@github.com/theappraisallane/tal-ng-translate.git#1.2.3",
"typescript-rest": "1.2.2",
"winston": "2.4.0",
"zone.js": "~0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.12.0",
"@angular-devkit/build-optimizer": "0.8.0",
"@angular/cli": "7.2.0",
"@angular/compiler-cli": "7.2.0",
"@compodoc/compodoc": "^1.1.2",
"@ngtools/webpack": "^7.2.1",
"@ngxs/devtools-plugin": "^3.2.0",
"@pact-foundation/karma-pact": "^2.1.9",
"@pact-foundation/pact": "^7.0.1",
"@pact-foundation/pact-web": "^7.0.2",
"@types/bluebird": "3.5.20",
"@types/config": "0.0.33",
"@types/dotenv": "^6.1.0",
"@types/express": "4.11.1",
"@types/googlemaps": "^3.30.4",
"@types/hammerjs": "^2.0.35",
"@types/helmet": "0.0.37",
"@types/jasmine": "2.8.6",
"@types/material-design-lite": "^1.1.15",
"@types/newrelic": "^3.3.0",
"@types/nock": "^9.1.2",
"@types/node": "^9.6.5",
"@types/request": "^2.47.0",
"@types/semver": "^5.5.0",
"@types/sinon": "^4.1.3",
"@types/socket.io-client": "^1.4.32",
"@types/source-map": "^0.5.0",
"@types/uglify-js": "^2.6.30",
"@types/webpack": "^3.8.8",
"@types/winston": "2.3.7",
"angular2-template-loader": "^0.6.2",
"autoprefixer": "^9.7.3",
"chai": "^4.2.0",
"codecov": "^3.1.0",
"codelyzer": "^4.2.1",
"copy-webpack-plugin": "^4.5.1",
"cross-env": "^5.1.4",
"css-loader": "^3.2.1",
"css-to-string-loader": "^0.1.3",
"cssnano": "^4.1.10",
"cucumber": "^5.1.0",
"dotenv": "^6.1.0",
"exports-loader": "^0.7.0",
"expose-loader": "^0.7.5",
"file-loader": "^1.1.11",
"find-root": "^1.1.0",
"gherkin-lint": "^3.3.0",
"gulp": "3.9.1",
"html-webpack-plugin": "3",
"husky": "^3.0.9",
"imports-loader": "^0.8.0",
"istanbul-instrumenter-loader": "~3.0.1",
"istanbul-merge": "^1.1.1",
"jasmine-core": "^2.99.1",
"jasmine-promise-wrapper": "^0.0.3",
"jasmine-ts": "^0.3.0",
"jsonlint-cli": "^1.0.1",
"karma": "^2.0.0",
"karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^1.1.1",
"karma-jasmine": "^1.1.0",
"karma-mocha-reporter": "^2.2.5",
"karma-remap-coverage": "^0.1.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.11",
"minifier": "^0.8.1",
"ng-router-loader": "^2.1.0",
"ngx-cookie-service": "^1.0.10",
"nock": "^9.2.3",
"npm-run-all": "^4.1.2",
"nyc": "^11.5.0",
"parse5": "^4.0.0",
"postcss-comment": "^2.0.0",
"postcss-cssnext": "^3.1.0",
"postcss-discard-comments": "^4.0.2",
"postcss-functions": "^3.0.0",
"postcss-import": "^12.0.1",
"postcss-inline-comment": "^3.0.0",
"postcss-loader": "^3.0.0",
"postcss-nested": "^4.2.1",
"postcss-preset-env": "^6.7.0",
"postcss-sassy-mixins": "^2.1.0",
"postcss-scss": "^2.0.0",
"postcss-strip-inline-comments": "^0.1.5",
"preload-webpack-plugin": "^2.3.0",
"protractor": "^5.4.2",
"protractor-cucumber-framework": "^6.1.2",
"raw-loader": "0.5.1",
"rimraf": "~2.6.2",
"sass-lint": "^1.12.1",
"sass-loader": "^8.0.0",
"script-ext-html-webpack-plugin": "^2.1.3",
"sinon": "^4.3.0",
"sinon-express-mock": "^2.0.0",
"source-map-loader": "^0.2.3",
"source-map-support": "^0.5.3",
"string-replace-loader": "~2.1.1",
"sugarss": "^2.0.0",
"tal-protractor-common": "git+ssh://git@github.com/theappraisallane/tal-protractor-common.git#1.4.1",
"to-string-loader": "^1.1.6",
"ts-loader": "^5.3.3",
"ts-node": "^7.0.1",
"tsc-watch": "^1.0.16",
"tslib": "^1.9.0",
"tslint": "~5.9.1",
"tslint-loader": "^3.5.3",
"typedoc": "^0.11.1",
"typescript": "3.2.2",
"uglifyjs-webpack-plugin": "^1.2.4",
"url-loader": "^1.0.1",
"webdriver-manager": "^12.1.7",
"webdriver-manager-replacement": "^2.0.2",
"webpack": "~4.41.2",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.1.2",
"webpack-dev-middleware": "^2.0.6",
"webpack-dev-server": "~3.9.0",
"webpack-hot-middleware": "^2.21.0",
"webpack-manifest-plugin": "^2.0.3",
"webpack-merge": "~4.1.2",
"webpack-pwa-manifest": "^3.6.2"
},
"engines": {
"node": ">= 8.0.0",
"npm": ">= 5",
"yarn": ">= 1.0.0"
},
"nyc": {
"include": [
"server/**/*.ts"
],
"exclude": [
"server/**/*.spec.js"
],
"reporter": [
"text-summary",
"json",
"html"
],
"extension": [
".ts",
".tsx"
],
"all": true,
"report-dir": "./coverage/server"
},
"snyk": true
}
Like I said in the question, the problem was the compiler was failing to compile the scss
files of the components when they try to import an scss
file from node_modules
.
I resolved it with sass-loader
applying the following rule for scss
in the webpack.config.common.js:
{
test: /\.component\.(css|sass|scss)$/,
use: [
'to-string-loader',
'css-loader',
'postcss-loader',
{
loader: 'sass-loader',
options: {
sassOptions: {
importer: url => {
if (url.startsWith('@angular/')) {
return {
file: path.resolve(`./node_modules/${url}`),
};
}
return null;
},
},
}
}
]
}
If I will have to import another library from node_module I should add another else if statement:
Example, let's say I have a custom library my-library in node_modules and I want to access to: node_modules/my-library/style.scss
so I have to add the following else if
statement:
{
test: /\.component\.(css|sass|scss)$/,
use: [
'to-string-loader',
'css-loader',
'postcss-loader',
{
loader: 'sass-loader',
options: {
sassOptions: {
importer: url => {
if (url.startsWith('@angular/')) {
return {
file: path.resolve(`./node_modules/${url}`),
};
else if (url.startsWith('my-library/')) {
return {
file: path.resolve(`./node_modules/${url}`),
};
}
return null;
},
},
}
}
]
}
With that, my project works again. Also it compiles with build
and serve
.
Finally, in my postcss.config.js I only need:
module.exports = {
plugins: [
require('autoprefixer')
]
}