afterwind-io / preprocessor-loader Goto Github PK
View Code? Open in Web Editor NEWBring the awesome "Conditional Compilation" to the Webpack, and more.
License: MIT License
Bring the awesome "Conditional Compilation" to the Webpack, and more.
License: MIT License
Using verbose mode in HTML files has unexpected problems, when using comments in a <style>
block.
If you have a css comment such as /* comment */
, verbose mode switches to use that style of comment for the remainder of the "block" of commented out code.
For example:
TEST = false
<!-- #!if TEST -->
<style>
#div1 {
display: flex;
flex-direction: column;
/* 1px because of reasons */
margin-left: 1px;
}
</style>
<div id="div1">
hello world
</div>
<!-- #!endif -->
the output becomes:
<!-- #!if TEST -->
<!-- <style> -->
<!-- #div1 { -->
<!-- display: flex; -->
<!-- flex-direction: column; -->
/* 1px because of reasons */
/* margin-left: 1px; */
/* } */
/* </style> */
/* <div id="div1"> */
/* hello world */
/* </div> */
<!-- #!endif -->
Which aren't html comments, and as such, the "div1" div is still rendered, although without the styling.
Putting a comment like:
</style>
<!-- fix comments -->
<div id="div1">
does sort of fix it, but it's not a great solution
Is there a feature that we can disable verbose mode for a block / for the singular file / for html files only?
This issue is the continuous discussion of #6.
TL;DR Currently there is no easy workaround to gracefully apply the preprocessor and keep an accurate source map at the same time.
The real problem here is, during the compilation, Angular just loads all the files from the file system. All content generated by last loader (webpack-preprocessor-loader
in this case) is neglected, which means:
angular.json
, with@angular-builders/custom-webpack
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "webpack.config.js",
"mergeStrategies": {
"module.rules": "append"
}
...with
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: "webpack-preprocessor-loader",
options: {
debug: false,
}
},
// ...
... actually does not work at all.
If we modify the mergeStrategies
to prepend
, another problem rises. The preprocessor
now takes js file, compiled by Angular, as its source. Currently the preprocessor
will not pass the source map to the next loader (and it should not, because the preprocessor
is meant to deal with the original code directly, thus no source map from upstream). As a result, the source map generated by Angular (js -> ts) is lost. So the final source map we do see in the inspector is a mapping between the bundled file and corresponding compiled code file, which is not intended. But the mapping is accurate though.
Then what about just passing the source map downward? Well, here is the reference from the earlier discussion:
Certain lines of code would likely be cut during preprocessing, and it can leads to potential problems. If source map generated from upstream does exist, it does not "know" the fact that some code will be trimmed afterwards, because the source map itself is based on the processed code from last step. In conclusion, the mapping would be broken and it will create offset after the cuts.
In conclusion, to handle the problem properly, the preprocessor
need to generate its own source map, and maybe based on the potential source map from upstream, which is far beyond the intended usage of this loader.
If anyone who is interested and has better idea/workaround, please feel free to leave your solution below. Thank you.
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
MapView,
TouchableHighlight,
TouchableOpacity,
ScrollView, ListView
} from 'react-native';
// import test from 'test';
// console.log('test', test);
let a = 1; // ...this line may be omitted.
// #!debug
a=2;
console.log("-----------a=",a);
const localAssets = {
// iconArrow: require('navigator/asset/icon_arrow.png'),
// iconArrow: require('../asset/icon_arrow.png'),
};
// let lazyModule =lazyRequire('./Page2.js');
// lazyModule.onModuleLoaded(function (module) {
// console.log('3@@@@@@@@@@@@@@@@@@@',module);
// });
//
// setTimeout(function(){
// let module = lazyModule.load();
// console.log('4@@@@@@@@@@@@@@@@@@@',module);
//
// }, 3000);
let ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => { return (r1.titleExtend != r2.titleExtend || r1.bizType != r2.bizType || r1.isFavoriteExtend !== r2.isFavoriteExtend) },
sectionHeaderHasChanged: (s1, s2) => { return s1 !== s2 },
getSectionHeaderData: (dataBlob, sectionID) => {
return DataManage.getSectionHeaderName(sectionID);
}
});
export default class Page1 extends Page {
constructor(props) {
super(props);
this.state = {
dataSource: ds.cloneWithRows(['John', 'Joel', 'James', 'Jimmy', 'Jackson', 'Jillian', 'Julie', 'Devin', "9", "10", "11 "]),
// dataSource: ds.cloneWithRowsAndSections(['John', 'Joel', 'James', 'Jimmy', 'Jackson', 'Jillian', 'Julie', 'Devin']),
loadState: 'normal', // 加载状态 normal-默认、loading-加载中、failed-加载失败、done-加载完成
};
// fetch("https://sec-m.ctrip.com/restapi/soa2/10245/json/GetMonitoring?_rm=0.8329076717428225")
// <LoadingFailedView />
}
render() {
console.log('page1:render');
return (
<ViewPort>
<View style={styles.container}>
{undefined}
<ListView
style={{ flex: 1 }}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
onEndReached={this.onLoadMore.bind(this)}
onEndReachedThreshold={300}
renderFooter={this.renderFooter.bind(this)}
pageSize={10}
/>
<Button
onPress={() => {
var img="";
img = require('navigator/asset/icon_arrow.png');
this.push('page2', { img });
}}
style={styles.button}>
Back to Page2
</Button>
<Button onPress={() => this._getLocation()} style={styles.button}>
点击定位
</Button>
<div {...this.props}>{undefined}{[undefined, <View>444</View>, undefined]} 333</div>
</View>
</ViewPort>
);
}
_getLocation() {
Location.locate({
locateLevel: 0,
timeout: 10000,
isForceLocate: true // 是否强制定位
//
}).then(
(data) => {
console.log("-----success located:", data);
}
).catch(
(err) => {
console.log("--------locate failed:", err);
}
);
}
renderRow(rowData, sectionID, rowID) {
if (!window.t1) {
window.t1 = rowData;
}
return (
<Text>Row data:{rowData}</Text>
)
}
onLoadMore() {
console.log('onLoadMore');
}
renderFooter() {
console.log('renderFooter');
return (<div>renderFooter</div>);
}
renderSeparator() {
// renderSeparator={this.renderSeparator.bind(this)}
// renderSectionHeader={this.renderSectionHeader.bind(this)}
console.log('renderSeparator');
return (<div>renderSeparator</div>);
}
renderSectionHeader() {
console.log('renderSectionHeader');
return (<div>renderSectionHeader</div>);
}
componentWillUnmount() {
// tp('Page1 componentWillUnmount')
// super.componentWillUnmount&&super.componentWillUnmount()
// this.unmounted = true;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
button: {
"backgroundColor": "red"
},
fillStyle: {
backgroundColor: 'red'
},
backgroundStyle: {
backgroundColor: 'orange',
borderRadius: 1
},
ProgressBar1: {
"marginTop": 20,
"width": 200
}
});
CRA gives you a webpack config that uses oneOf:
for the loaders, how do I use this preprocessor with that???
Wondering if I should deal with it like the eslint pre-processor....
rules: [
// Disable require.ensure as it's not a standard language feature.
{ parser: { requireEnsure: false, }, },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
enforce: 'pre',
use: [
{
options: {
cache: true,
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
resolvePluginsRelativeTo: __dirname,
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [
/\.bmp$/,
/\.gif$/,
/\.jpe?g$/,
/\.png$/
,
],
loader: require.resolve('url-loader'),
options: {
limit: imageInlineSizeLimit,
name: 'static/media/[name].[hash:8].[ext]',
},
},
// Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
presets: [ 'react-app', ],
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent:
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
},
},
},
],
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
compact: isEnvProduction,
},
},
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true, },
],
],
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
// Babel sourcemaps are needed for debugging into node_modules
// code. Without the options below, debuggers like VSCode
// show incorrect code and set breakpoints on the wrong lines.
sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap,
},
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use MiniCSSExtractPlugin to extract that CSS
// to a file, but in development "style" loader enables hot editing
// of CSS.
// By default we support CSS Modules with the extension .module.css
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
}),
},
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [
/\.(js|mjs|jsx|ts|tsx)$/,
/\.html$/,
/\.json$/
,
],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
I guess this is more of a feature request than a bug per se, but when you have an #!if
that usually requires an #!endif
, but it's missing, no error is produced.
Is it possible to add an option, that if at the end of the file it is expecting an #!endif
, and there isn't one, it can throw / log an error?
For example, say this is a file:
function MyFunc() {
let x = "foo";
#!if debug
x = "bar"
}
Because there is no closing #!endif
, it means the rest of the file will be removed on non-debug builds.
Can an option be added where this will still have the same behaviour as now, but also logs this out as an error / warning if this missing #!endif
is detected?
有没有考虑出vite 版的
Does this loader compatible with esbuild-loader? I tried to make it worked, but my statements are not evaluated at compile time.
When using a function in params, it is not defined for the first time that file is processed, however, on subsequent run (such as the second time using webpack watch), it is defined correctly.
We use the 'thread-loader', and removing that does seem to make this work fine.
However, with it enabled, the first time the file is processed, it produces and error.
I can tell it works on subsequent processing with "watch" because putting a console.log in the function prints to console.
The error is:
includeID is not defined
at eval (eval at ifComparator (node_modules\webpack-preprocessor-loader\dist\filter.js:134:24), <anonymous>:3:1)
As far as I can tell, this is only the case for functions, and normal values are perfectly fine.
So, for example, we have this:
const Config = {
units: ["Foo", "Baz"]
}
...
{
loader: 'webpack-preprocessor-loader',
options: {
params: {
includeID: function(id: string) {
return Config.units.includes(id);
}
}
}
And then the file looks like this:
// #!if includeID("Foo")
import {} from 'units/Foo';
// #!endif
// #!if includeID("Bar")
import {} from 'units/Bar';
// #!endif
// #!if includeID("Baz")
import {} from 'units/Baz';
// #!endif
Some other things which might affect it:
The webpack config is done in TS not JS.
We use 'thread-loader', and removing that works.
Hi, @afterwind-io
I've found this loader does not work in the case when having export default from another imported default module.
For example,
// File ./components/a.js
const someFunc = () => { // do something };
export default someFunc;
// File ./components/index.js
import someFunc from "./a"
export default someFunc;
// File app.js
import someFunc from "./components"
someFunc();
It will throw errors when compling
WARNING in app.js
"export 'default' (imported as 'someFunc') was not found in 'app.js'
It would be glad to see any response from you.
Thanks.
After updated to the version v1.2.1 I got compilations error,
This happens when option verbose
is set to the ``true.
Part of the original code:
useTopWindow: false,
/**
* @property {Boolean} hashbang If set to `true`, when a hash is set, the hash will be prefixed
* with an exclamation making it a hash bang instead of just a hash.
*
* Ext.util.History.add('foo'); // will result in #foo
*
* Ext.util.History.hashbang = true;
* Ext.util.History.add('bar'); // will result in #!bar
*/
/**
* @property {String} currentToken The current token.
* @private
*/
/**
* @event ready
* Fires when the Ext.util.History singleton has been initialized and is ready for use.
* @param {Ext.util.History} history The Ext.util.History singleton.
*/
/**
* @event change
* Fires when navigation back or forwards within the local page's history occurs.
* @param {String} token An identifier associated with the page state at that point
* in its history.
*/
hashRe: /^(#?!?)/,
Error:
ERROR in ../../../softvisio-node/ext/resources/ext-7.6.0/ext.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: d:\projects\softvisio-node\ext\resources\ext-7.6.0\ext.js: Unexpected token (66015:6)
66013 | /* * @property {String} currentToken The current token.*/
66014 | /* * @private*/
> 66015 | /* */*/
| ^
66016 | /**
66017 | * @event ready
66018 | * Fires when the Ext.util.History singleton has been initialized and is ready for use.
at in
Vue3,vue-cli
Nesting #!if statements in other #!if statements doesn't resolve how you would expect it to.
Having a param such as debug
set to true
, and then doing the following:
// #!if debug
// #!if !debug
// #!if debug
console.log("hello")
// #!endif
// #!else
console.log("world")
// #!endif
// #!endif
You would expect it to output console.log("world")
, however, console.log("hello")
is produced.
Changing the first line to // #!if !debug
doesn't change the output either, even though you would then expect neither line to exist.
Also, having something like this:
// #!if !debug
// #!if debug && !debug
console.log("hello")
// #!endif
console.log("world")
// #!endif
debug
is true, so the expected output would be for the whole block to excluded. However, both #!if
statements terminate at the first #!endif
, and so regardless of the value of debug
, console.log("world")
is always outputted.
Tested on [email protected]
& [email protected]
As described above
It doen‘t work
RT
/* common.scss */
.test {
// #!if NODE_ENV === 'production'
color: red;
// #!elseif NODE_ENV === 'development'
color: blue;
// #!endif
}
/* index.scss */
@import './common.scss'
当前 NODE_ENV
为 'development'
,index.scss
最终编译后的是 color: red
。
Hi, @afterwind-io
Is there any chance you could introduce how can I config the loader for the .html
, .scss
and other types of source files?
Thanks.
Hello, does it work with test libraries ?
How to use it with jest ?
Currently the if directive is implemented by the following function:
export function ifComparator(params: IParamsMap, rawCondition: string): boolean {
const keys = Object.keys(params);
const values = keys.reduce((v, key) => v.concat(params[key]), []);
const comparator = new Function(...keys, `return ${rawCondition};`);
return comparator(...values);
}
v.concat
may cause wrong result if the value type is Array. Just a little snippet:
const testObj = { a: 1, b: [0, 2, 3] };
const keys = Object.keys(testObj);
const values = keys.reduce((v, key) => v.concat(testObj[key]), []);
keys
is ["a", "b"]
values
is [1, 0, 2, 3]
(new Function(...keys, 'return b;'))(...values)
will return 0
, and this is an unexpected result.
A more robust way is to use push
instead of concat
.
Causes the condition compilation of html to fail
demo.vue:
<!-- #!if IS_PC -->
<p>PC</p>
<!-- #!elseif IS_M -->
<p>M</p>
<!-- #!endif -->
result:
<p>PC</p>
<p>M</p>
My npm config:
"webpack-preprocessor-loader"
: "^1.1.4"
,"vue-loader"
: "^16.8.1"
,When cache
is enabled with webpack 5, modifying the preprocessor options will not trigger a cache bust and thus will use the cached version of the last compiled file as if the options were not changed
现在的写法是
// #!debug
console.log('1234');
直观感受是"非(!)debug"
换成下面的是不是会好点
// !#debug
console.log('1234');
或者这个语法可以自定义
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.