1.安装
npm init -y //生成默认的package.json文件
npm i webpack webpack-cli --save-dev //安装在 devDependencies依赖里面
2.默认配置文件 webpack.config.js
./node_modules/.bin/webpack
//运行此指令,没有指令配置文件,默认是 webpack.config.js
//没有设置npm指令,直接调用依赖里面的webpack,后面会用npm指令,自动到依赖里面去查找
3.webpack.config.js
const path=require("path");
module.exports={
mode:"production", //生产环境
entry: "./src/index.js", //入口
//打包的出口
output:{
path: path.resolve(__dirname,'dist'), //打包的路径
filename:"bundle.js" //打包后的文件名字
}
}
// index.html
// 目前没用插件,先自己写个
Document
4.Entry 入口
// entry String是单入口,Object,Array是多入口
// 如果传入一个字符串或字符串数组,chunk 会被命名为 main。
// 如果传入一个对象,则每个键(key)会是 chunk 的名称,该值描述了 chunk 的入口起点
module.exports={
entry: "./src/index.js"
}
module.exports={
entry: ["./src/index.js", "./src/behind.js"]
}
//上面两种打包后的文件都是 bundle.js
//下面这种会出现 app.js和 behind.js
module.exports = {
mode: "production",
entry: {
app: "./src/index.js",
behind: "./src/behind.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js"
}
}
5. Output 输出
5-1. filename 用的是占位符的模式
filename: "[name].[hash].js"
//每次构建过程中,唯一的 hash 生成
//就算是多入口的,每个入口的hash都是一样的,因为是同样的一次构建
filename: "[name].[chunkhash].js"
// 基于每个 chunk 内容的 hash
// 当通过多个入口起点(entry point)、代码拆分(code splitting)或各种插件(plugin)创建多个 bundle
filename: "[name].[id].js"
// 使用内部 chunk id
5-2. library
webpack 还可以用于打包 JavaScript library,比如 你正在编写一个名为 webpack-numbers
的小的 library,可以将数字 1 到 5 转换为文本表示,反之亦然,例如将 2 转换为 'two'
// 对于用途广泛的 library,我们希望它能够兼容不同的环境,例如 CommonJS,AMD,Node.js 或者作为一个全局变量。
// 为了让你的 library 能够在各种用户环境(consumption)中可用,需要在 output 中添加 library 属性
filename: 'webpack-numbers.js',
library: 'webpackNumbers'
// 当你在 import 引入模块时,这可以将你的 library bundle 暴露为名为 webpackNumbers 的全局变量
// 因为libraryTarget默认是var 全局变量
// 为了让 library 和其他环境兼容,还需要在配置文件中添加 libraryTarget 属性
filename: 'webpack-numbers.js',
library: 'webpackNumbers',
libraryTarget: 'umd'
// 变量:作为一个全局变量,通过 script 标签来访问(libraryTarget:'var')
// this:通过 this 对象访问(libraryTarget:'this')
// window:通过 window 对象访问,在浏览器中(libraryTarget:'window')
// UMD:在 AMD 或 CommonJS 的 require 之后可访问(libraryTarget:'umd')
// libraryTarget: "global" - 入口起点的返回值将使用 output.library 中定义的值,分配给 global 对象的这个属性下
5-3. publicPath
对于按需加载(on-demand-load)或加载外部资源(external resources)(如图片、文件等)来说,output.publicPath 是很重要的选项。如果指定了一个错误的值,则在加载这些资源时会收到 404 错误。
该选项的值是以 runtime(运行时) 或 loader(载入时) 所创建的每个 URL 为前缀。因此,在多数情况下,此选项的值都会以/
结束
publicPath: "/assets/",
// 对于上面的配置,会多加/assets/路径
// 一个输出 HTML 的 loader 可能会像这样输出:
// 加载 CSS 的一个图片: background-image: url(/assets/spinner.gif);
// 类型:
publicPath: "https://cdn.example.com/assets/", // CDN(总是 HTTPS 协议)
publicPath: "//cdn.example.com/assets/", // CDN (协议相同)
publicPath: "/assets/", // 相对于服务(server-relative)
publicPath: "assets/", // 相对于 HTML 页面
publicPath: "../assets/", // 相对于 HTML 页面
publicPath: "", // 相对于 HTML 页面(目录相同)
6.Babel v7
6-1. @babel/core 必须安装*
babel 的核心功能包含在 @babel/core 模块中,要是进行代码转换的一些方法,可以将源代码根据配置转换成兼容目标环境的代码
6-2.@babel/cli (可选)
是 babel 提供的命令行工具,用于命令行下编译源代码
6-3.@babel/plugin* (插件,按需求添加在plugins里面)
babel是通过插件来进行代码转换的,例如箭头函数使用@babel/plugin-transform-arrow-functions插件来进行转换
6-4.@babel/presets (js语法比较全面的插件包) 基本必须*
上面用的@babel/plugin-transform-arrow-functions解决箭头函数的转化,但是这只是一项功能
我们代码中仍然残留了其他 ES2015+ 的特性,我们希望对它们也进行转换
所以我们可以用官方的presets
@babel/preset-env支持所有现代的 JavaScript 的新特性
V7的stage的预设,已经被废弃
Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API
----这是因为babel 把 Javascript 语法分为syntax 和 api
----api 指那些我们可以通过 函数重新覆盖的语法 ,类似 includes, map, includes, Promise, 凡是我们能想到重写的都可以归属到api。
----syntax 指像箭头函数,let,const,class, 依赖注入 Decorators等等这些
所以要用到垫片(@babel/polyfill)
6-5.@babel/polyfill
// @babel/polyfill由core-js2和regenerator-runtime组成
// 最新版本是core-js3,@babel/polyfill不支持从core-js2到core-js3的平滑过渡
-----所以会出现下面的提醒 start------
WARNING: We noticed you're using the useBuiltIns option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the corejs option.
------ end ------
// 使用方法只要设置env的参数corejs: 3
// core-js3有很多优点,首先就是新,包含很多新特性,其次就是可以配合@babel/runtime,实现实例方法的支持
useBuiltIns:false(default):此时不对 polyfill 做操作。如果引入 @babel/polyfill,则无视配置的浏览器兼容,引入所有的 polyfill。(不太推荐)
useBuiltIns:"entry":根据配置的浏览器兼容,引入浏览器不兼容的 polyfill。需要在入口文件手动添加垫片,会自动根据 browserslist 替换成浏览器不兼容的所有 polyfill。
可以配合:
import "core-js/stable"
import "regenerator-runtime/runtime"
此第二方案可行,但是文件还是会大点
useBuiltIns:"usage":不需要在文件顶部手动引入@babel/polyfill,会根据代码中的使用进行按需添加,不包括实例方法
到此为止,仍然会有2个问题(上面的第二方案除外,已经用了corejs和runtime):
1.高阶语法向低阶语法转化时引入了了很多helper函数(如_classCallCheck)。当文件数量很多时,每个文件都引入这些helper函数会使得文件体积增大,怎么这些helper函数抽离到单独的模块,然后按需引入呢
2.虽然polyfill是按需引入的,但是会污染全局命名空间,当你写的是公共库时,可能会与使用者本地的方法产生冲突。例如你在你的库中引入了polyfill中的Promise,使用者自身定义了自己的Promise,这就容易产生冲突。如何将你的公共库中引入的polyfill api隔离起来呢
------解决方案(transform-runtime 按需加载方案 的升级策略:)
@babel/runtime-corejs3 @babel/plugin-transform-runtime ------
问题一:
@babel/runtime以避免编译输出中的重复。运行时将编译到您的构建中。
@babel/runtime依赖 @babel/helpers和 regenerator-runtime,helper函数都可以从这里面引入,实现共享,但是不能自动引入
为了解决自动引入问题,于是 babel 提供了 @babel/plugin-transform-runtime 来替我们做这些转换
问题二:
@babel/runtime-corejs3是一个不会污染全局变量的,里面包含可以转换promises,symbols, collections, iterators等在内的polyfills,所以会比@babel/runtime能转换的东西多一些(包含@babel/runtime)
安装依赖后使用: @babel/plugin-transform-runtime option中的corejs参数
core-js3,实例方法这个问题得到了完美解决,具体配置如下
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"modules": false //对ES6的模块文件不做转化,以便使用tree shaking、sideEffects等
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
// .browserslistrc
> 1%
last 2 versions
not ie <= 8
依赖:
devDependencies:{
"@babel/core": "^7.6.0",
"@babel/plugin-transform-runtime": "^7.6.2",
"@babel/preset-env": "^7.6.0",
"babel-loader": "^8.0.6"
},
dependencies:{
"@babel/runtime-corejs3": "^7.6.3",
"core-js": "^3.2.1",
}
Babel6和Babel7的runtime和transform-runtime对比:
7.splitChunks 抽取公共部分
optimization: {
splitChunks: {
chunks: "all", //async表示异步,initial表示入口文件,all包含所有
minSize: 30000, //模块的最小体积 单位bytes
minChunks: 1, //在拆分之前共享模块的最小块数
maxAsyncRequests: 5, //按需加载的最大并行请求数
maxInitialRequests: 3, //一个入口最大并行请求数
automaticNameDelimiter: '~', //连接符
name: true, //抽取出来文件的名字,默认为 true,表示自动生成文件名
//缓存组,可以理解为分块的条件
//特有的字段:
//test条件,priority优先级,
//reuseExistingChunk:表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的
//cacheGroups是一个对象,其中键是缓存组名称。所有的上面列出的选项是可能的:chunks,minSize,minChunks,maxAsyncRequests,maxInitialRequests,name,会覆盖外层的
cacheGroups: {
//定义base开头的公共块,包含 react 和 react-dom,优先级最高
common: {
name: "base",
test: /(react|react-dom)/,
priority: -5
},
//定义vendors开头的公共块,node_modules文件下的都在里面,比上面优先级低
vendors: {
name: "vendors",
test: /[\\/]node_modules[\\/]/,
priority: -10
},
//定义default开头的公共块,最少引用是2次,覆盖外层的一次
default: {
name: "default",
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
},
runtimeChunk: {
name: "manifest" //生成manifest(runtime逻辑)
}
}
打包文件:default的暂不满足条件,所以没有这个打包文件
8.多页面打包方案
const path = require("path");
const glob = require("glob");
const HtmlWebpackPlugin = require("html-webpack-plugin");
//多页面的通用方法
exports.setMulEntry = () => {
const entry = {}; //入口
const htmlWebpackPlugins = []; //多页面
//glob进行遍历,遍历的条件自己定义
//这里是src/pages/页面名字文件夹/index.js
//结果是个路径的数组
const entryFiles = glob.sync(
path.resolve(__dirname, "../src/pages/*/index.js")
);
if (entryFiles && entryFiles.length > 0) {
entryFiles.forEach(entryFile => {
const match = entryFile.match(/src\/pages\/(.*)\/index\.js/); //匹配路径
const pageName = match && match[1]; //获取路径上面的页面的名字
entry[pageName] = entryFile; //设置入口的key
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, `../dist/${pageName}.html`), //名字
template: path.resolve(__dirname, `../htmls/${pageName}.html`), //模板
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
minifyJS: true,
minifyCSS: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
chunksSortMode: "dependency",
chunks: [pageName, "manifest", "vendors", "base"] //依赖,根据SplitChunks,必要
})
);
});
}
return {
entry,
htmlWebpackPlugins
};
};
9.eslint+prettier格式化js代码
//.eslintrc.js
module.exports = {
//根目录,不会再往上父级查找
root: true,
//全局变量的环境
env: {
browser: true,
node: true,
es6: true
},
parser: "babel-eslint", // 解析器
parserOptions: {
ecmaVersion: 7, //es的版本
sourceType: "module", //es的模块
ecmaFeatures: {
"jsx": true
}
},
// 插件的格式 plugin:插件名/配置名称
// 插件名可以省略 eslint-plugin-
// 省略包名的前缀eslint-config-
// 扩展库
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:prettier/recommended"
],
// 可以省略 eslint-plugin-
// 插件
plugins: [
"react"
],
// 插件的规则格式: 插件名/规则ID
// 自定义规则
rules: {
"prettier/prettier": "error", //prettier的格式不正确报错
"react/jsx-uses-react": "error", //plugin:react/recommended的格式不正确报错
"react/jsx-uses-vars": "error", //plugin:react/recommended的格式不正确报错
}
}
// eslint-loader
// 下面的加到webpack的module的rules的配置里面即可
exports.userEsLint = () => ({
test: /\.js$/,
loader: "eslint-loader",
enforce: "pre", //前置
include: [path.resolve(__dirname, "../src")], //限制src的js
options: {
formatter: require("eslint-friendly-formatter"), //ESLint的简单格式化程序/报告程序
emitWarning: true
}
});
// 依赖
devDependencies:{
"babel-eslint": "^10.0.3",
"eslint": "^6.5.1",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^3.0.2",
"eslint-plugin-react": "^7.16.0",
"eslint-config-prettier": "^6.4.0",
"prettier": "^1.18.2",
"eslint-plugin-prettier": "^3.1.1",
}
10. stylelint规范css,scss,less等样式
// .stylelintrc.js
module.exports = {
extends: "stylelint-config-standard",
rules:{}
}
const StyleLintPlugin = require("stylelint-webpack-plugin");
plugins: [
new StyleLintPlugin({
files: ["**/*.{html,vue,css,sass,scss}"], //要格式化的文件
context: "src", //上下文
fix: true //保存时候是否自动修复,因为没有像eslint的快捷键,所以要设置下
})
]
// 依赖
devDependencies:{
"stylelint": "^11.1.1",
"stylelint-config-standard": "^19.0.0",
"stylelint-webpack-plugin": "^1.0.2",
}
11.postcss的配置(配合postcss-loader)
// .postcssrc.js
module.exports = {
"plugins": {
"postcss-import": {}, //引用node_modules模块的css的import
"autoprefixer": {} //补充前缀
}
}
css通用loader配置:
// ------css loader的通用配置方法-------
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
exports.cssLoaders = options => {
options = options || {};
const cssLoader = {
loader: "css-loader",
options: {
sourceMap: options.sourceMap
}
};
const postcssLoader = {
loader: "postcss-loader",
options: {
sourceMap: options.sourceMap
}
};
function generateLoaders(loader, innerLoaderOption) {
//根据options参数usePostCss判断是否加postcss-loader
const loaders = options.usePostCss
? [cssLoader, postcssLoader]
: [cssLoader];
// 如果参数有loader,就加到loaders数组里面去,一般是解析scss,less之类的
if (loader) {
loaders.push({
loader: `${loader}-loader`,
options: { ...innerLoaderOption, sourceMap: options.sourceMap } //内部配置参数
});
}
// 参数options是否需要分离css,用MiniCssExtractPlugin
if (options.extract) {
return [MiniCssExtractPlugin.loader].concat(loaders);
}
return ["style-loader"].concat(loaders);
}
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders("less"),
sass: generateLoaders("sass", { indentedSyntax: true }),
scss: generateLoaders("sass")
}; //针对不用的格式使用不用的loader数组
};
//组合成webpack的module的rules的格式
exports.styleLoaders = options => {
const output = [];
const loaders = exports.cssLoaders(options);
for (const extension in loaders) {
output.push({
test: new RegExp(`\\.${extension}$`),
use: loaders[extension]
});
}
return output;
};
12.gzip压缩
// compression-webpack-plugin插件
// 新版没有asset选项,用filename
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require("compression-webpack-plugin");
prodConfig.plugins.push(
new CompressionWebpackPlugin({
filename: "[path].gz[query]", // 目标资源名称
// 匹配
test: new RegExp(
"\\.(" + config.build.productionGzipExtensions.join("|") + ")$"
),
threshold: 10240, // 只处理比这个值大的资源 单位:字节
minRatio: 0.8 // 只有压缩率比这个值小的资源才会被处理
})
);
}
如何看gzip压缩已经成功开启:
打开chrome的Content-Encoding,如果有gzip就表示该文件启用了,没有就表示没有启用:
13.配置文件
// webpack.base.conf.js
const path = require("path");
const config = require("./config");
const StyleLintPlugin = require("stylelint-webpack-plugin");
const utils = require("./config/utils");
function resolve(dir) {
return path.resolve(__dirname, dir);
}
module.exports = {
entry: utils.setMulEntry().entry,
output: {
path: config.dev.assetsRoot,
filename: "[name].js",
publicPath:
process.env.NODE_ENV === "production"
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: [".js", ".scss", ".css"],
alias: {
"@": resolve("src"),
"@img": resolve("src/images")
}
},
module: {
rules: [
...[utils.userEsLint()],
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader"]
},
{
test: /\.(jpg|png|gif|svg)$/,
exclude: /node_modules/,
use: [
{
loader: "url-loader",
options: {
limit: 10240,
name: utils.assetsPath("img/[name].[hash:7].[ext]")
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: "url-loader",
options: {
limit: 10000,
name: utils.assetsPath("fonts/[name].[hash:7].[ext]")
}
}
]
},
plugins: [
new StyleLintPlugin({
files: ["**/*.{html,vue,css,sass,scss}"],
context: "src",
fix: true
})
]
};
// webpack.dev.conf.js
const baseConfig = require("./webpack.base.conf");
const merge = require("webpack-merge");
const config = require("./config");
const webpack = require("webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const FriendlyErrorsPlugin = require("friendly-errors-webpack-plugin"); //友好错误console界面
const portfinder = require("portfinder");
const path = require("path");
const utils = require("./config/utils");
const HOST = process.env.HOST;
const PORT = process.env.PORT && Number(process.env.PORT);
const devConfig = merge(baseConfig, {
mode: "development",
module: {
rules: utils.styleLoaders({
sourceMap: config.dev.cssSourceMap,
usePostCss: true
})
},
devtool: config.dev.devtool,
devServer: {
clientLogLevel: "warning",
hot: true,
contentBase: false, //CopyWebpackPlugin替代
host: HOST || config.dev.host || "localhost",
port: PORT || config.dev.port || 8080,
open: config.dev.autoOpenBrowser, //是否运行自动开启浏览器
//覆盖全屏错误
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable || {}, //代理
quiet: true,
//监听的配置
watchOptions: {
ignored: /node_modules/,
poll: config.dev.poll
}
},
plugins: [
...utils.setMulEntry().htmlWebpackPlugins, //多页面配置
new webpack.DefinePlugin({
"process.env": require("./config/dev.env")
}),
new webpack.HotModuleReplacementPlugin(), //热加载插件
new webpack.NamedModulesPlugin(), // 展示更新的文件名
//在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段。这样可以确保输出资源不会包含错误
new webpack.NoEmitOnErrorsPlugin(),
//复制static的静态文件
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, "static"),
to: config.dev.assetsSubDirectory,
ignore: [".*"]
}
])
]
});
//这是错误弹框,可以没有
const createNotifierCallback = () => {
const notifier = require("node-notifier");
return (severity, errors) => {
if (severity !== "error") return;
const error = errors[0];
notifier.notify({
title: "my-webpack",
message: severity + ": " + error.file
});
};
};
// portfinder处理端口的冲突 如8080自动变为8081
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = PORT || devConfig.devServer.port;
portfinder.getPort((err, port) => {
if (err) {
reject(err);
} else {
process.env.PORT = port;
devConfig.devServer.port = port;
devConfig.plugins.push(
new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [
`webpack应用的系统地址: http://${devConfig.devServer.host}:${port}`
]
},
onErrors: config.dev.notifyOnErrors
? createNotifierCallback()
: undefined
})
);
resolve(devConfig);
}
});
});
// webpack.prod.conf.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const merge = require("webpack-merge");
const utils = require("./config/utils");
const config = require("./config");
const webpack = require("webpack");
const env = require("./config/prod.env");
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const baseConfig = require("./webpack.base.conf");
const prodConfig = merge(baseConfig, {
mode: "production",
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
usePostCss: true
})
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath("js/[name].[chunkhash:8].js"),
chunkFilename: utils.assetsPath("js/[name]-[id]-[chunkhash:8].js")
},
optimization: {
splitChunks: {
chunks: "all",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: "~",
name: true,
cacheGroups: {
common: {
name: "base",
test: /(react|react-dom)/,
priority: -5
},
vendors: {
name: "vendors",
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
name: "default",
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
},
runtimeChunk: {
name: "manifest"
}
},
plugins: [
...utils.setMulEntry().htmlWebpackPlugins,
new webpack.DefinePlugin({
"process.env": env
}),
new MiniCssExtractPlugin({
filename: utils.assetsPath("css/[name].[hash:8].css")
}),
new webpack.HashedModuleIdsPlugin(), // 用于vender的moudule.id会根据解析递增的问题
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, "./static"),
to: config.build.assetsSubDirectory,
ignore: [".*"]
}
]),
//清理文件,排除.bat的启动文件
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ["**/*", "!*.bat"]
})
]
});
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require("compression-webpack-plugin");
prodConfig.plugins.push(
new CompressionWebpackPlugin({
filename: "[path].gz[query]", // 目标资源名称
// 匹配
test: new RegExp(
"\\.(" + config.build.productionGzipExtensions.join("|") + ")$"
),
threshold: 10240, // 只处理比这个值大的资源 单位:字节
minRatio: 0.8 // 只有压缩率比这个值小的资源才会被处理
})
);
}
if (config.build.bundleAnalyzerReport) {
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); //分析打包文件体积大小
prodConfig.plugins.push(new BundleAnalyzerPlugin());
}
module.exports = prodConfig;
// config的文件夹
// dev.env.js
const merge = require("webpack-merge");
const prodEnv = require("./prod.env");
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
});
// prod.env.js
module.exports = {
NODE_ENV: '"production"'
};
// index.js
const path = require("path");
module.exports = {
dev: {
proxyTable: {},
assetsSubDirectory: "static",
assetsPublicPath: "/",
// host: "172.18.1.164",
// port: 8080,
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false,
useEslint: false,
showEslintErrorsInOverlay: false,
devtool: "cheap-module-eval-source-map",
cacheBusting: true,
cssSourceMap: true,
assetsRoot: path.resolve(__dirname, "../dist")
},
build: {
// Paths
assetsRoot: path.resolve(__dirname, "../dist"),
assetsSubDirectory: "static",
assetsPublicPath: "/",
productionSourceMap: true,
devtool: "source-map",
productionGzip: true,
productionGzipExtensions: ["js", "css"],
bundleAnalyzerReport: false
}
};
// utils.js 通用配置方法
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const path = require("path");
const glob = require("glob");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const config = require("./index");
exports.assetsPath = pathname => {
const prev =
process.env.NODE_ENV === "production"
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory;
return path.posix.join(prev, pathname);
};
exports.cssLoaders = options => {
options = options || {};
const cssLoader = {
loader: "css-loader",
options: {
sourceMap: options.sourceMap
}
};
const postcssLoader = {
loader: "postcss-loader",
options: {
sourceMap: options.sourceMap
}
};
function generateLoaders(loader, innerLoaderOption) {
const loaders = options.usePostCss
? [cssLoader, postcssLoader]
: [cssLoader];
if (loader) {
loaders.push({
loader: `${loader}-loader`,
options: { ...innerLoaderOption, sourceMap: options.sourceMap }
});
}
if (options.extract) {
return [MiniCssExtractPlugin.loader].concat(loaders);
}
return ["style-loader"].concat(loaders);
}
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders("less"),
sass: generateLoaders("sass", { indentedSyntax: true }),
scss: generateLoaders("sass")
};
};
exports.styleLoaders = options => {
const output = [];
const loaders = exports.cssLoaders(options);
for (const extension in loaders) {
output.push({
test: new RegExp(`\\.${extension}$`),
use: loaders[extension]
});
}
return output;
};
exports.setMulEntry = () => {
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(
path.resolve(__dirname, "../src/pages/*/index.js")
);
if (entryFiles && entryFiles.length > 0) {
entryFiles.forEach(entryFile => {
const match = entryFile.match(/src\/pages\/(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, `../dist/${pageName}.html`),
template: path.resolve(__dirname, `../htmls/${pageName}.html`),
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
minifyJS: true,
minifyCSS: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
chunksSortMode: "dependency",
chunks: [pageName, "manifest", "vendors", "base"]
})
);
});
}
return {
entry,
htmlWebpackPlugins
};
};
exports.userEsLint = () => ({
test: /\.js$/,
loader: "eslint-loader",
enforce: "pre",
include: [path.resolve(__dirname, "../src")],
options: {
formatter: require("eslint-friendly-formatter"),
emitWarning: true
}
});
项目目录: