import path from 'path';
import webpack from 'webpack';
import postcssPresetEnv from 'postcss-preset-env';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import {
compile,
getCompiler,
getErrors,
getExecutedCode,
getModuleSource,
getWarnings,
readAsset,
} from './helpers/index';
jest.setTimeout(10000);
describe('"sourceMap" option', () => {
describe('not specified', () => {
it('should not generate source maps', async () => {
const compiler = getCompiler('./source-map/basic.js');
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.css', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
});
describe('true', () => {
it('should generate source maps', async () => {
const compiler = getCompiler('./source-map/basic.js', {
sourceMap: true,
});
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.css', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps #2', async () => {
const compiler = getCompiler('./source-map/with-query.js', {
sourceMap: true,
});
const stats = await compile(compiler);
expect(
getModuleSource('./source-map/with-query.css?url=false', stats)
).toMatchSnapshot('module');
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when previous loader does not generate source maps', async () => {
const compiler = getCompiler(
'./source-map/basic.js',
{},
{
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: true },
},
{
loader: path.resolve(
__dirname,
'./fixtures/source-map-loader.js'
),
options: {
sourceMap: null,
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.css', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when previous loader generates source maps without "sourceRoot"', async () => {
const absolutePath = path.resolve(
__dirname,
'fixtures',
'source-map',
'basic.css'
);
const compiler = getCompiler(
'./source-map/basic.js',
{},
{
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: true },
},
{
loader: path.resolve(
__dirname,
'./fixtures/source-map-loader.js'
),
options: {
sourceMap: JSON.stringify({
version: 3,
sources: [absolutePath],
names: [],
mappings: 'AAAA,6BAA6B;;AAE7B;EACE,UAAU;AACZ',
file: absolutePath,
sourcesContent: [
'@import "./nested/nested.css";\n\n.class {\n color: red;\n}\n',
],
}),
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.css', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when previous loader generates different source in source maps', async () => {
const absoluteSourceRoot = path.resolve(
__dirname,
'fixtures',
'source-map'
);
const absolutePath = path.resolve(absoluteSourceRoot, 'basic-1.css');
const relativePath = path.relative(
absoluteSourceRoot,
path.resolve(__dirname, 'fixtures', 'source-map', 'basic-2.css')
);
const compiler = getCompiler(
'./source-map/basic.js',
{},
{
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: true },
},
{
loader: path.resolve(
__dirname,
'./fixtures/source-map-loader.js'
),
options: {
sourceMap: JSON.stringify({
version: 3,
sourceRoot: absoluteSourceRoot,
sources: [
absolutePath,
relativePath,
'https://example.com/foo.css',
'//example.com/foo.css',
`</foo/bar/baz.css`,
],
names: [],
mappings: 'AAAA,6BAA6B;;AAE7B;EACE,UAAU;AACZ',
file: absolutePath,
sourcesContent: [
'@import "./nested/nested.css";\n\n.class {\n color: red;\n}\n',
'a { color: red; }',
'a { color: green; }',
'a { color: blue; }',
'a { color: azure; }',
],
}),
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.css', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when previous loader generates source maps with "sourceRoot"', async () => {
const absoluteSourceRoot = path.resolve(
__dirname,
'fixtures',
'source-map'
);
const compiler = getCompiler(
'./source-map/basic.js',
{},
{
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: true },
},
{
loader: path.resolve(
__dirname,
'./fixtures/source-map-loader.js'
),
options: {
sourceMap: JSON.stringify({
foo: 'bar',
version: 3,
sources: ['basic.css'],
sourceRoot: absoluteSourceRoot,
names: [],
mappings: 'AAAA,6BAA6B;;AAE7B;EACE,UAAU;AACZ',
file: 'basic.css',
sourcesContent: [
'@import "./nested/nested.css";\n\n.class {\n color: red;\n}\n',
],
}),
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.css', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when previous loader generates source maps ("postcss-loader")', async () => {
const compiler = getCompiler(
'./source-map/basic-postcss.js',
{},
{
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: {
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [postcssPresetEnv({ stage: 0 })],
},
sourceMap: true,
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(
getModuleSource('./source-map/basic.postcss.css', stats)
).toMatchSnapshot('module');
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when previous loader generates source maps ("sass-loader")', async () => {
const compiler = getCompiler(
'./source-map/basic-scss.js',
{},
{
module: {
rules: [
{
test: /\.s[ca]ss$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: {
sourceMap: true,
},
},
{
loader: 'sass-loader',
options: {
implementation: require('sass'),
sourceMap: true,
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.scss', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when previous loader generates source maps ("less-loader")', async () => {
const compiler = getCompiler(
'./source-map/basic-less.js',
{},
{
module: {
rules: [
{
test: /\.less$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: {
sourceMap: true,
},
},
{
loader: 'less-loader',
options: {
sourceMap: true,
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(getModuleSource('./source-map/base.less', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when previous loader generates source maps ("stylus-loader")', async () => {
const compiler = getCompiler(
'./source-map/basic-styl.js',
{},
{
module: {
rules: [
{
test: /\.styl$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: {
sourceMap: true,
},
},
{
loader: 'stylus-loader',
options: {
sourceMap: true,
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(getModuleSource('./source-map/base.styl', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps and do not change "[contenthash]" on different platform', async () => {
const compiler = getCompiler(
'./source-map/basic.js',
{},
{
devtool: 'source-map',
context: path.resolve(__dirname, '..'),
output: {
path: path.resolve(__dirname, './outputs'),
filename: '[name].[contenthash].bundle.js',
chunkFilename: '[name].[contenthash].chunk.js',
publicPath: '/webpack/public/path/',
},
module: {
rules: [
{
test: /\.css$/i,
rules: [
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: true },
},
],
},
],
},
}
);
const stats = await compile(compiler);
const chunkName = Object.keys(
stats.compilation.assets
).find((assetName) => /\.js$/.test(assetName));
expect(chunkName).toBe(
webpack.version[0] === '5'
? 'main.12fc65e5279709bf97a4.bundle.js'
: 'main.0c7724aa71a6ab6d45fd.bundle.js'
);
expect(
getModuleSource('fixtures/source-map/basic.css', stats)
).toMatchSnapshot('module');
expect(getExecutedCode(chunkName, compiler, stats)).toMatchSnapshot(
'result'
);
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when css was extracted', async () => {
const compiler = getCompiler(
'./source-map/extract.js',
{},
{
devtool: 'source-map',
output: {
path: path.resolve(__dirname, '../outputs'),
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js',
publicPath: '/webpack/public/path/',
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
module: {
rules: [
{
test: /\.css$/i,
rules: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: true },
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(readAsset('main.css', compiler, stats)).toMatchSnapshot(
'extracted css'
);
expect(
JSON.parse(readAsset('main.css.map', compiler, stats))
).toMatchSnapshot('source map');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when css was extracted and do not change "[contenthash]" on different platform', async () => {
const compiler = getCompiler(
'./source-map/extract.js',
{},
{
devtool: 'source-map',
context: path.resolve(__dirname, '..'),
output: {
path: path.resolve(__dirname, '../outputs'),
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js',
publicPath: '/webpack/public/path/',
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
module: {
rules: [
{
test: /\.css$/i,
rules: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: true },
},
],
},
],
},
}
);
const stats = await compile(compiler);
const chunkName = Object.keys(
stats.compilation.assets
).find((assetName) => /\.css$/.test(assetName));
const extractedCSS = readAsset(chunkName, compiler, stats);
expect(chunkName).toBe(
webpack.version[0] === '5' ? 'main.0c4134a13ce273487b87.css' : chunkName
);
expect(
extractedCSS.replace(
/=(.+?)\..+?\.css\.map/,
'=$1.xxxxxxxxxxxxxxxxxxxx.css.map'
)
).toMatchSnapshot('extracted css');
const sourceMap = JSON.parse(
readAsset(`${chunkName}.map`, compiler, stats)
);
sourceMap.file = sourceMap.file.replace(
/^(.+?)\..+?\.css$/,
'$1.xxxxxxxxxxxxxxxxxxxx.css'
);
sourceMap.sources = sourceMap.sources.map((source) =>
source.replace('css-loader', '')
);
expect(sourceMap).toMatchSnapshot('source map');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
});
describe('false', () => {
it('should not generate source maps', async () => {
const compiler = getCompiler('./source-map/basic.js', {
sourceMap: false,
});
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.css', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should not generate source maps when previous loader does not generate source maps', async () => {
const compiler = getCompiler(
'./source-map/basic.js',
{},
{
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: false },
},
{
loader: path.resolve(
__dirname,
'./fixtures/source-map-loader.js'
),
options: {
sourceMap: undefined,
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.css', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should not generate source maps when previous loader generates source maps', async () => {
const compiler = getCompiler(
'./source-map/basic.js',
{},
{
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: false },
},
{
loader: path.resolve(
__dirname,
'./fixtures/source-map-loader.js'
),
options: {
sourceMap: JSON.stringify({
foo: 'bar',
version: 3,
sources: ['basic.css'],
names: [],
mappings: 'AAAA;EACE,UAAU;AACZ',
file: 'basic.css',
sourcesContent: ['.class {\n color: red;\n}\n'],
}),
},
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(getModuleSource('./source-map/basic.css', stats)).toMatchSnapshot(
'module'
);
expect(
getExecutedCode('main.bundle.js', compiler, stats)
).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
it('should generate source maps when css was extracted', async () => {
const compiler = getCompiler(
'./source-map/extract.js',
{},
{
output: {
path: path.resolve(__dirname, '../outputs'),
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js',
publicPath: '/webpack/public/path/',
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
module: {
rules: [
{
test: /\.css$/i,
rules: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: path.resolve(__dirname, '../src'),
options: { sourceMap: false },
},
],
},
],
},
}
);
const stats = await compile(compiler);
expect(readAsset('main.css', compiler, stats)).toMatchSnapshot(
'extracted css'
);
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
});
});