webpack静态打包
举例
目录结构
│ .babelrc
│ .eslintrc
│ .gitignore
│ .prettierrc.json
│ LICENSE
│ package.json
│ postcss.config.js
│ tsconfig.json
│ webpack.config.js
├─config
│ cdn.js
│ rules.js
├─dist
├─logs
├─scripts
│ build.js
│ common.js
│ dev.js
│ log.js
│ pages.json
└─src
│ index.html
├─apps
├─demo
├─demo2
├─assets
├─axios
├─components
└─utils
关于 store,使用的是 royjs
业务代码
-
demo/app.jsx
import { Button, Form, Card } from 'antd'; import FormItem from '@components/Form'; import { inject } from '@royjs/core'; import store from './model/store' @inject(store) export default class App extends React.Component { render() { const { count } = this.props.state; return <Card> <Form labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} > <FormItem label='数字输入' component='inputNumber'></FormItem> <FormItem label='输入' component='input'></FormItem> <FormItem label='选择器' component='select'></FormItem> <FormItem label='多选' component='checkbox'></FormItem> <Form.Item> <Button onClick={() => this.props.dispatch('add')}> </Button> </Form.Item> </Form> </Card> } }
-
demo/main.jsx
import App from './app'; ReactDOM.render(<App />, document.getElementById('container'));
-
demo/model/store.js
import { Store } from '@royjs/core'; export default new Store({ state : { count: 0 }, actions:{ add(state, payload){ state.count }, reduce(state, payload){ state.count -- } } })
对常见的组件进行封装
以 Form
为例
-
components/Form/const.js
import { Input, InputNumber, DatePicker, Select, Radio, Checkbox, Rate, Slider, TimePicker, Upload, TreeSelect, Cascader, Transfer } from "antd"; const { RangePicker } = DatePicker; const { TextArea } = Input; export const Com = { 'input': Input, 'inputPassword': Input.Password, 'textArea': TextArea, 'inputNumber': InputNumber, 'datePicker': DatePicker, 'rangePicker': RangePicker, 'select': Select, 'radio': Radio, 'radioGroup': Radio.Group, 'checkbox': Checkbox, 'rate': Rate, 'slider': Slider, 'timePicker': TimePicker, 'upload': Upload, 'treeSelect': TreeSelect, 'cascader': Cascader, 'transfer': Transfer }
-
components/Form/index.jsx
import React, { Component } from 'react' import { Form } from 'antd'; import { Com } from './const' export default class FormItem extends Component { render() { const { label, name, component, required, ...rest } = this.props; const ReCompont = Com[component]; const formProps = { label, name, component, required } return ( <Form.Item {...formProps} rules={required ? [{ required: true }] : []} > <ReCompont style={{ minWidth: '100px' }} {...rest}></ReCompont> </Form.Item> ) } }
-
index.html
资源使用CDN引入,例:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title><%= title %></title> <link rel="icon" href="favicon.ico" /> <% for (const i in externals_css) {%> <link rel="stylesheet" href="<%= externals_css[i] %>" /> <%} %> </head> <body> <div id="container"></div> <% for (const i in externals_js) {%> <script crossorigin src="<%= externals_js[i] %>"></script> <%} %> </body> </html>
webpack 基本配置
以下为抽取静态html为例
webpack.config.js
const { Configuration, DefinePlugin, ProgressPlugin } = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const CompressionPlugin = require('compression-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const path = require('path')
const rules = require('./config/rules')
const pagesJSON = require('./scripts/pages.json')
const { js, css } = require('./config/cdn')
const packageJSON = require('./package.json')
/**
* @type {Configuration}
*/
module.exports = (env, args) => {
const mode = args.mode
const pages = env.pages.split(',')
const srcPagesDir = path.resolve(__dirname, 'src/apps/')
const entry = {}
const otherParams = {}
;(env.otherParams || '').split(',').forEach((item) => {
otherParams[item.split('=')[0]] = item.split('=')[1]
})
console.log('正在编译以下应用', pages)
pages.forEach((el) => (entry[el] = path.resolve(srcPagesDir, el, 'main.jsx')))
const config = {
entry,
mode,
output: {
filename: (pathData) => {
return pages.includes(pathData.runtime)
? '[name]/js/[contenthash:10].js'
: '[name].js'
},
path: path.resolve(__dirname, `dist/@${packageJSON.name}`),
publicPath: `/@${packageJSON.name}/`,
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
minify: (file, sourceMap) => {
const uglifyJsOptions = {
sourceMap: false,
}
return require('uglify-js').minify(file, uglifyJsOptions)
},
}),
],
splitChunks: {
chunks: 'async',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
module: {
rules,
},
resolve: {
extensions: ['.js', '.jsx', '.tsx', '.ts'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@axios': path.resolve(__dirname, 'src/axios'),
'@assets': path.resolve(__dirname, 'src/assets'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
externals: {
react: 'React',
'react-dom': 'ReactDOM',
antd: 'antd',
mobx: 'mobx',
'mobx-react': 'mobxReact',
classnames: 'classNames',
axios: 'axios',
qs: 'Qs',
},
plugins: [
...pages.map((pageName) => {
const pageInfo =
pagesJSON.find((item) => item.pageName === pageName) || {}
return new HtmlWebpackPlugin({
filename: `${pageName}/index.html`,
chunks: [pageName],
template: path.resolve(__dirname, 'src/index.html'),
templateParameters: (compilation, assets, assetTags, options) => {
const externals_js = []
const externals_css = []
const externalsValues = []
for (let [key, value] of compilation._modules.entries()) {
if (key.includes('external')) {
externalsValues.push(value.userRequest)
}
}
js.forEach((item) => {
externalsValues.forEach((val) => {
if (item.externalsName === val) {
externals_js.push(item.url)
}
})
})
css.forEach((item) => {
externalsValues.forEach((val) => {
if (item.externalsName === val) {
externals_css.push(item.url)
}
})
})
if (mode === 'production') {
console.log('---------------------------------')
console.log(`正在写入模板 页面:${pageName}/index.html: cdn/js`)
console.log(externals_js)
console.log(`正在写入模板 页面:${pageName}/index.html: cdn/css`)
console.log(externals_css)
console.log('---------------------------------')
}
return {
title: `remons.cn - ${pageInfo.title}`,
externals_js: [...new Set(externals_js)],
externals_css: [...new Set(externals_css)],
}
},
})
}),
new ProgressPlugin({
activeModules: true,
modules: true,
}),
// 提取单独的CSS
new MiniCssExtractPlugin({
filename: '[name]/main.[contenthash:10].css',
}),
new DefinePlugin({
APP_NAME: JSON.stringify(`@${packageJSON.name}`),
}),
// 压缩css
new CssMinimizerPlugin(),
new BundleAnalyzerPlugin({
defaultSizes: 'stat',
analyzerMode:
mode === 'production' && otherParams.report === 'true'
? 'server'
: 'disabled',
}),
mode === 'production' && otherParams.gzip === 'true'
? new CompressionPlugin()
: null,
].filter((_) => !!_),
devServer: {
static: {
directory: path.resolve(__dirname, 'dist'),
},
compress: true,
port: 3033,
host: 'local-ip',
open: [`/@${packageJSON.name}/${env.pages.split(',')[0]}`],
hot: true,
client: {
progress: true,
},
},
stats: 'errors-only',
devtool: mode === 'development' ? 'eval-source-map' : false,
}
return config
}
config/rules.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const path = require('path')
// test: the applied rule.
// exclude: ignore processed files.
// use: multiple loaders can be used to run from bottom to top.
// Handle compatibility
const postcssLoader = {
loader: 'postcss-loader',
}
const cssMoudleLoader = {
loader: 'css-loader',
options: {
sourceMap: false,
modules: {
localIdentName: '[path][name]-[local]-[hash:base64:10]',
},
},
}
const rules = [
{
oneOf: [
// css
{
test: /\.css$/,
include: path.resolve(__dirname, '../src'),
// MiniCssExtractPlugin.loader plug-in extracts css as a separate file.
// Unlike style-loader, style-loader inserts css into the style tag.
use: [MiniCssExtractPlugin.loader, 'css-loader', postcssLoader],
},
// less
{
test: new RegExp(`^(?!.*\\.global).*\\.less`),
include: path.resolve(__dirname, '../src'),
include: path.resolve(__dirname, '../src'),
use: [
MiniCssExtractPlugin.loader,
cssMoudleLoader,
postcssLoader,
'less-loader',
],
},
{
test: new RegExp(`^(.*\\.global).*\\.less`),
include: path.resolve(__dirname, '../src'),
use: [
MiniCssExtractPlugin.loader,
'css-loader',
postcssLoader,
'less-loader',
],
},
// Process images.
{
test: /\.(jpg|png|jpeg|gif)$/,
include: path.resolve(__dirname, '../src'),
use: [
{
loader: 'url-loader',
options: {
limit: 1024 * 8,
name: '[name].[ext]',
esModule: false,
outputPath: 'static/assets/images',
},
},
],
},
// Static resources in HTML
{
test: /\.html$ /,
include: path.resolve(__dirname, '../src'),
loader: 'html-loader',
options: {
esModule: false,
},
},
{
test: /\.js|jsx$/,
include: path.resolve(__dirname, '../src'),
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /\.ts|tsx$/,
include: path.resolve(__dirname, '../src'),
use: {
loader: 'ts-loader',
},
},
// Other resources
{
test: /\.(pdf|doc|node|svg)$/,
use: [
{
loader: 'file-loader',
options: {
outputPath: (url, resourcePath) => {
console.log(resourcePath);
return `${
(resourcePath || '')
.replace(/\//g, '_')
.replace(/\\/g, '_')
.split('apps')[1]
.split('_')
.filter((_) => !!_)[0]
}/assets/file/${url}`
},
},
},
],
},
],
},
]
module.exports = rules
config/cdn.js
const js = [
{ externalsName: 'react', url: "https://unpkg.com/react@17.0.2/umd/react.production.min.js" },
{ externalsName: 'react-dom', url: "https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js" },
{ externalsName: 'qs', url: "https://unpkg.com/qs@6.10.3/dist/qs.js" },
{ externalsName: 'axios', url: "https://unpkg.com/axios/dist/axios.min.js" },
{ externalsName: 'classnames', url: "https://unpkg.com/classnames@2.3.1/index.js" },
{ externalsName: 'mobx', url: "https://unpkg.com/mobx@6.3.2/dist/mobx.umd.production.min.js" },
{ externalsName: 'mobx-react', url: "https://unpkg.com/mobx-react-lite@3.1.6/dist/mobxreactlite.umd.production.min.js" },
{ externalsName: 'mobx-react', url: "https://unpkg.com/mobx-react@7.3.0/dist/mobxreact.umd.production.min.js" },
{ externalsName: 'moment', url: "https://unpkg.com/moment@2.29.1/min/moment.min.js" },
{ externalsName: 'antd', url: "https://unpkg.com/moment@2.29.1/min/moment.min.js" },
{ externalsName: 'antd', url: "https://unpkg.com/antd@4.18.9/dist/antd.min.js" },
];
const css = [
{ externalsName: 'antd', url: "https://unpkg.com/antd@4.18.9/dist/antd.min.css", },
]
module.exports = {
js, css
}
postcss.config.js
module.exports = {
plugins: {
autoprefixer: {
overrideBrowserslist: [
"ie >= 8", // 兼容IE7以上浏览器
"Firefox >= 3.5", // 兼容火狐版本号大于3.5浏览器
"chrome >= 35", // 兼容谷歌版本号大于35浏览器,
"opera >= 11.5", // 兼容欧朋版本号大于11.5浏览器
],
},
},
};
执行打包命令
执行命令的配置文件
scripts/common.js
const fsExtra = require('fs-extra')
const { readdirSync } = require('fs')
const fs = require('fs')
const log4js = require('log4js')
const chalk = require('chalk')
const { logCongfig } = require('./log')
const pakeageJSON = require('../package.json')
log4js.configure(logCongfig)
const deletePath = (pages) =>
Promise.all(
pages.map((item) => fsExtra.remove(`dist/@${pakeageJSON.name}/${item}`))
)
const getPages = () => {
return new Promise(async (resolve, reject) => {
const pages =
((process.argv[2] || '').includes('=') ? '' : process.argv[2]) ||
readdirSync('src/apps').join(',')
if (!pages) {
reject('未找到目录')
return
}
const set1 = new Set(pages.split(','))
const set2 = new Set(readdirSync('src/apps'))
let difference = new Set([...set1].filter((x) => !set2.has(x)))
if (difference.size) {
console.error('识别到以下目录不存在,请检查:')
reject([...difference].join(','))
return
}
console.log('识别到以下目录:')
const otherParams =
process.argv.findIndex((i) => (i || '').includes('=')) !== -1
? process.argv.slice(
process.argv.findIndex((i) => i.includes('=')),
process.argv.length
)
: []
pages.split(',').forEach((el, index) => console.log(`${index 1}:`, el))
await deletePath(pages.split(','))
resolve({ pages, otherParams })
})
}
const getDist = (pages) => {
let filesDir = []
const readDirSize = (path) => {
fs.readdir(path, {}, (err, files) => {
if (files) {
files.forEach((item) => {
readDirSize(`${path}/${item}`)
})
} else {
fs.stat(path, (err, stats) => {
filesDir.push(
`${path.replace('dist', '')}__size__${(stats.size / 1024).toFixed(
2
)}`
)
})
}
})
}
readDirSize('dist')
setTimeout(() => {
const size = filesDir
.map((item) => item.split('__size__')[1])
.reduce((a, b) => a b)
.toFixed(2)
console.log(
`dist: 合计大小 ${size > 1024 ? (size / 1024).toFixed(2) : size} ${
size > 1024 ? 'mb' : 'kb'
}, 资源如下:`
)
const newFileArr = pages.map((page) => {
return {
page,
files: filesDir
.sort()
.map((item) => {
if (
item
.replace(/\//g, '_')
.replace(/\\/g, '_')
.split('_')
.filter((_) => !!_)
.includes(page)
) {
return item
}
})
.filter((_) => !!_),
}
})
newFileArr.forEach((item) => {
console.log(chalk.green(' '))
const filesSize = item.files
.map((item) => item.split('__size__')[1])
.reduce((a, b) => a b)
.toFixed(2)
console.log(
chalk.bgGreen(
chalk.black(
` ${item.page}:`,
`${filesSize > 1024 ? (filesSize / 1024).toFixed(2) : filesSize} ${
filesSize > 1024 ? 'mb' : 'kb'
} `
)
)
)
item.files.forEach((el) => {
console.log(
chalk.green(el.split('__size__')[0]),
chalk.yellow(`${el.split('__size__')[1]} kb`)
)
})
})
console.log(
'如需查看打包详细依赖和大小,请重新执行命令: npm run build (your apps) report=true 或 npm run build report=true'
)
}, 100)
}
module.exports = {
getPages,
getDist,
}
scripts/build.js
const { execSync } = require("child_process");
const { getPages, getDist } = require('./common');
getPages().then(({ pages, otherParams }) => {
if ((otherParams || '').includes('report=true')) {
const command = `webpack --mode=production --env pages=${pages} otherParams=${otherParams}`;
execSync(command, { stdio: "inherit" });
getDist();
} else {
pages.split(',').forEach((page, index) => {
const command = `webpack --mode=production --env pages=${page} otherParams=${otherParams}`;
execSync(command, { stdio: "inherit" });
console.log([page], '打包完成', `共 ${pages.split(',').length} 个应用,当前已打包 ${index 1} 个`);
if (index === pages.split(',').length - 1) {
console.log('========所有应用已打包完成========');
getDist(pages.split(','));
}
})
}
})
scripts/dev.js
const { execSync } = require("child_process");
const { getPages } = require('./common');
getPages().then(({ pages, otherParams }) => {
const command = `webpack serve --mode=development --env pages=${pages} otherParams=${otherParams}`;
execSync(command, { stdio: "inherit" });
});
scripts/log.js
略(log4js)
scripts/pages.json
// 用来生成 META TITLE
[
{
"pageName": "demo",
"title": "demo 测试"
},
]
package.json
{
"name": "website_pages",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node scripts/dev.js",
"dev": "npm start",
"build": "node scripts/build.js"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.14.6",
"@babel/plugin-proposal-decorators": "^7.14.5",
"@babel/plugin-transform-runtime": "^7.14.5",
"@babel/preset-env": "^7.14.7",
"@babel/preset-react": "^7.14.5",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.12",
"babel-loader": "^8.2.2",
"chalk": "^4.1.2",
"compression-webpack-plugin": "^8.0.1",
"css-loader": "^5.2.6",
"css-minimizer-webpack-plugin": "^3.0.2",
"extract-loader": "^5.1.0",
"file-loader": "^6.2.0",
"fs-extra": "^10.0.1",
"html-loader": "^2.1.2",
"html-webpack-plugin": "^5.3.2",
"husky": "^7.0.4",
"less": "^4.1.1",
"less-loader": "^10.0.1",
"log4js": "^6.4.2",
"mini-css-extract-plugin": "^2.1.0",
"postcss": "^8.3.5",
"postcss-loader": "^6.1.1",
"postcss-preset-env": "^6.7.0",
"prettier": "^2.5.0",
"style-loader": "^3.0.0",
"ts-loader": "^9.2.7",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.5.5",
"url-loader": "^4.1.1",
"webpack": "^5.44.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.9.0",
"uglify-js": "^3.15.5"
},
"dependencies": {
"antd": "^4.16.8",
"axios": "^0.24.0",
"classnames": "^2.3.1",
"crypto-js": "^4.1.1",
"methods-r": "^1.2.14",
"mobx": "^6.3.5",
"mobx-react": "^7.2.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
打包命令
- dev :
npm run dev 文件夹名称
例:npm run dev demo1,demo2
- build:
npm run build 文件夹名称
例:npm run build demo1,demo2
- 注意:默认会查找
src/pages
目录下的文件夹,最终打包产物会在根目录dist
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhghegbf
系列文章
更多
同类精品
更多
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13