Library
开发一个library,使用者可能的三种引入方式
import library from 'library'
const library = require('library')
require(['library'], function(){
})
<script src="library.js"></script>
<script>
// library.math
</script>
webpack.config.js
{
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library', // 支持html src引入
libraryTarget: 'umd', // 支持通用引入方式
libraryTarget: 'this' // this / window,将不支持前三种方式,但是library将挂在this上
},
externals: ["lodash"] // 忽略lodash
}
发布到npm
- 注册npm
- 命令行 npm adduser 输入用户名 密码
- npm publish
A 打包库组件
除了可打包应用,也可打包js库 实现一个大整数加法库的打包
- 需要打包压缩版和非压缩版本
- 支持AMD/CJS/ESM模块引入
库的目录结构和打包要求
打包输出的库名称:
- 未压缩 large-number.js
- 压缩 large-number.min.js
/dist large-number.js large-number.min.js webpack.config.js packae.json index.js /src index.js
ES module
import * as largeNumber from 'large-number'
//...
largeNumber.add('999', '1')
CJS
const largeNumbers = require('large-number')
//...
largeNumber.add('999', '1')
AMD
require(['large-number'],function(large-number){
//...
largeNumber.add('999', '1')
})
直接使用script引入
<html>
<script src="https://unpkg.com/large-number"/>
<script>
//...
//Global variable
largeNumber.add('999','1')
//Property in the window object
window.largeNumber.add('999','1')
</script>
</html>
如何将库暴露出去?
library: 指定库的全局变量 libraryTarget: 支持库引入的方式
mkdir large-number
cd large-number
npm init -y
npm i webpack webpack-cli -D
webpack.config.js
module.exports={
entry:{
"large-number":"./src/index.js",
"large-number.min":"./src/index.js"
},
output:{
filename: "[name].js",
library:"largeNumber",
libraryExport: "default",
libraryTarget:"umd"
}
}
package.json
"scripts": {
"build":"webpack"
}
src/index.js
export default function add(a, b){
let i = a.length -1;
let j = b.length -1;
let carry = 0;
let ret='';
while(i>=0 || j >= 0){
let x = 0;
let y = 0;
let sum;
if(i >= 0){
x = a[i]-"0";
i--
}
if(j >= 0){
y = b[j]-"0";
j--
}
sum = x + y + carry;
if(sum >= 10){
carry = 1;
sum -= 10;
}else{
carry= 0;
}
ret = sum + ret;
}
if(carry){
ret = carry + ret;
}
return ret;
}
build后,发现dist目录中large-number.js和large-number.min都被压缩了。 下面通过include配置只对.min压缩
npm i terser-webpack-plugin -D
const TerserPlugin = require('terser-webpack-plugin')
module.exports={
mode:"none",
//...
optimization:{
minimize: true,
minimizer: [
new TerserPlugin({
include:/\.min\.js$/,
})
]
}
}
设置入口文件
package.json 的 main 字段为index.js package.json
{
"main":"index.js",
"scripts":{
"build":"webpack",
"prepublish":"webpack" // npm publish 时打包
}
}
index.js
if(process.env.NODE_ENV==="production"){
module.exports = require("./dist/large-number.min.js")
}else{
module.exports = require("./dist/large-number.js")
}
最后打包发到npm
npm publish
引入
npm i large-bumber -S
B SSR 服务端渲染
优化白屏时间 对SEO友好
渲染:html+css+js+data ==> 渲染后的HTML
服务端: 所有模块等资源都存储在服务端 内网机器捞取数据更快 一个HTML返回所有数据
浏览器和服务器交互流程
| 客户端渲染 | 服务端渲染 | |
|---|---|---|
| 请求 | 多个请求(HTML、数据等) | 1个请求 |
| 加载过程 | HTML&数据串行加载 | 1个请求返回HTML&数据 |
| 渲染 | 前端渲染 | 服务端渲染 |
| 可交互 | 图片等静态资源加载完成,JS逻辑执行完成可交互 |
总结:服务端渲染SSR的核心是减少请求
实现思路
服务端
- 使用 react-dom/server 的 renderToString 方法将React组件渲染成字符串
- 服务端路由返回对应的模板
客户端
- 打包出针对服务端的组件
package.json
"scripts": {
"build:ssr":"webpack --config webpack.ssr.js"
},
webpack.ssr.js
const setMPA = () => {
//...
const entryFiles = glob.sync(path.join(__dirname, './src/*/index-server.js'))
Object.keys(entryFiles)
.map((index) => {
const match = entryFile.match(/src\/(.*)\/index-server\.js/)
//...
const pageName = match && match[1]
if(!pageName){
retrun
}
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
//...
)
}
}
const { entry, htmlWebpackPlugins } = setMPA()
module.exports = {
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]-server.js',
libraryTarget: 'umd'
},
}
search/index-server.js
'use strict';
const React = require('react')
const logo = require('./img/FireAnt.png')
require('./search.less')
class Search extends React.Component {
constructor(props) {
super(props)
this.state = {
Text: null
}
}
loadComponent = () => {
import('./test.js').then((Text) => {
this.setState({
Text: Text.default
})
})
}
render() {
const {Text} = this.state;
return <div className="search-text">
Search Text132123123
{ Text ? <Text /> : 'null' }
<img src={logo} onClick={this.loadComponent} />
</div>
}
}
module.exports = <Search/>;
server/index.js
if(typeof window === 'undefined'){
global.window = {} // node没有window
}
const express = require('express')
const {renderToString} = require('react-dom/server')
const SSR = require('../dist/search-server')
const server = (port) =>{
const app = express()
app.use(express.static('dist'))
app.get('/search', (req, res)=>{
const html = renderMarkup(renderToString(SSR))
res.status(200).send(html)
})
app.listen(port, ()=>{
console.log('Server is running on port:', port);
})
}
server(process.env.PORT || 3000)
const renderMarkup = (str)=>{
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root">${str}</div>
</body>
</html>`;
}
webpack打包存在的问题
浏览器的全局变量(Node.js中没有document, window)
- 组件适配:将不兼容的组件根据打包环境进行适配
- 请求适配:将fetch或者ajax发送请求改成isomorphic-fetch 或 axios
样式问题(Node.js中无法解析css)
- 方案一:服务端打包通过ignore-loader忽略掉CSS的解析
- 方案二:将style-loader替换为isomorphic-style-loader (CSS in JS 写法,不推荐)
解决css不显示问题
使用打包出来的浏览器端html为模板 设置占位符,动态插入组件
search/index.html
<div id="root"><!--HTML_PLACEHOLDER--></div>
server/index.js
const fs = require('fs')
const path = require('path')
const template = fs.readFileSync(path.join(__dirname, '../dist/search.html'), 'utf-8')
//...
const renderMarkup = (str)=>{
return template.replace('<!--HTML_PLACEHOLDER-->',str);
}
首屏数据如何处理?
服务端获取数据 替换占位符
search/index.html
<div id="root"><!--HTML_PLACEHOLDER--></div>
<!--INITIAL_DATA__PLACEHOLDER-->
server/index.js
const data = require('./data.json')
//...
const renderMarkup = (str)=>{
const dataStr = JSON.stringify(data)
return template.replace('<!--HTML_PLACEHOLDER-->',str)
.replace('<!--INITIAL_DATA_PLACEHOLDER-->', `<script>window.__initial_data=${dataStr}</script>`);
}
C 优化构建时命令行的显示日志
当前构建日志展示很多日志,对于业务开发者,其中多数不需要关注
统计信息stats
| Preset | Alternative | Description |
|---|---|---|
| "errors-only" | none | 只在发生错误时输出 |
| "minimal" | none | 只在发生错误或有新的编译时输出 |
| "none" | false | 没有输出 |
| "normal" | true | 标准输出 |
| "verbose" | none | 全部输出 |
我们使用'errors-only' webpack.prod.js
module.exports = {
//...
stats: 'errors-only'
}
npm run build 后没有提示 webpack.dev.js
module.exports = {
devServer:{
contentBase: './dist',
hot: true,
stats: 'errors-only'
}
}
npm run dev后提示 Compiled successfully.
如何优化命令行的构建日志
使用 friendly-errors-webpack-plugin
- success: 构建成功的日志提示
- warning: 构建警告的日志提示
- error:构建报错的日志提示
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name][chunkhash:8].js',
path: __dirname + '/dist'
},
plugins: [
new FriendlyErrorsWebpackPlugin()
],
stats: 'errors-only'
}
D 构建异常和中断处理
如何判断构建是否成功?
在CI/CD的pipline 或者发布系统需要知道当前构建状态 每次构建完后输入 echo $? 获取错误码
webpack4 之前标本构建失败不会抛出错误码 NodeJS中的 process.exit规范
- 0 表成功完成,回调函数中,err 为 null
- 非 0 表执行失败,回调函数中,err 不为 null, err.code 就是传给 exit 的数字
如何主动捕获并处理构建错误?
compiler 在每次构建结束后会触发done这个hook process.exit主动处理构建报错
plugins: [
function(){
this.hooks.done.tap('done', (stats)=>{
if(
stats.compliation.errors &&
stats.compilation.errors.length &&
process.argv.indexOf('--watch')==-1){
console.log('build error');
process.exit(1)
}
})
}
]