本文但愿能帮助那些一直用脚手架而对工程化没有概念的朋友。javascript
文中许多步骤会在运行时报错,从错误中分析须要添加的配置,加深印象以及对所添加的每一行配置的理解。css
本文将以
React
为例,带你走一次。html
mkdir demo && cd demo
npm init
touch index.js
复制代码
yarn add webpack webpack-cli --dev # 安装webpack相关依赖
touch webpack.config.js # 新建webapck配置文件
复制代码
这是一份最基本的webpack配置:前端
const path = require('path');
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
}
};
复制代码
在package.json中,添加scipts
以下:java
"scripts": {
"build": "webpack"
},
复制代码
在命令行中执行node
npm run build
复制代码
你会看到以下警告:react
WARNING in configuration The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: webpack.js.org/concepts/mo…webpack
因为webpack
在4.0后,新增mode配置项,它为咱们默认设置了production
。可是该警告并无影响build
的结果暂且忽略。 能够看到dist
目录已经生成了名为my-first-webpack.bundle.js
文件。git
OK,最基本的配置已经完成,接下来引入React。程序员
yarn add react react-dom # 安装react相关依赖
复制代码
在使用前,须要在dist目录中,添加index.html
,内容以下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>React</title>
</head>
<body>
<div id="root"></div>
<script src="./my-first-webpack.bundle.js"></script>
</body>
</html>
复制代码
从react官网官网找段HelloWorld贴过来吧,全英文看不懂?不要紧,代码你总认识吧,贴就完了!
将index.js
中的内容变动以下:
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
复制代码
OK,看似完美,执行下,build
看看效果。
ERROR in ./index.js 4:16
Module parse failed: Unexpected token (4:16)
You may need an appropriate loader to handle this file type.
| import ReactDOM from 'react-dom';
|
> ReactDOM.render(<div>Hello World</div>,
| document.getElementById('root'));
复制代码
报错了?莫慌,从错误信息中找关键字,You may need an appropriate loader to handle this file type.
。 这时候感慨一句,程序员是伟大的,错误信息很详细了,告诉咱们须要适当的loader
来处理这个文件。什么loader
?别问我,不是我干的,继续往下看刚才的连接下一小节react-jsx的介绍,拉到最下面,是否是有一段关于官方的建议?继续查找关键字,是否是看到一个叫Babel
的东西?纳尼,莫非jsx
和它有关系?虽然这段话是说推荐编辑器设置的吧,可是程序员必备一颗好奇的心。
Google一下上面的关键词Babel
,进去瞅瞅吧。 因而开始接触一个新名词Babel
,这玩意干啥的?能为咱们带来什么?看看首页吧。
ES2015 及更高版本
Babel 经过语法转换器支持最新版本的 JavaScript 。
Polyfill
因为 Babel 只转换语法(如箭头函数), 你可使用 babel-polyfill 支持新的全局变量,例如 Promise 、新的原生方法。
JSX 和 Flow
Babel 可以转换 JSX 语法并去除类型注释。
可插拔
Babel 是创建在插件以外的。 你可使用已有的插件或者本身编写插件来组成属于你本身的转换管道。
可调式
支持 Source map 所以能够轻松调试编译后代码。
看完首页的介绍,是否和我有一样的感叹:好东西啊!既然是好东西,用起来吧。 从配置的webpack
选项中,你会发现刚才出现的两个关键字都来了babel
、loader
,艾玛,得来全不费工夫。
yarn add babel-loader babel-core babel-preset-env babel-polyfill babel-preset-react --dev
# 这波安装比较长?由于咱们把刚才看到的es201五、更高版本语法的,polyfill,jsx的都装上了
复制代码
config
配置将webpack.config.js
修改配置以下:
const path = require('path');
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
]
}
};
复制代码
.babelrc
配置文件touch .babelrc #建立.babelrc
复制代码
将如下内容粘贴至.babelrc
中:
{
"presets": ["env", "react"]
}
复制代码
至此,已经将上面的babel-preset-env
、babel-preset-react
使用上了,那polyfill
怎么用呢?继续看波文档吧。emmm,写的很清楚了,咱们把polyfill
使用上吧,修改webpack.config.js
中的配置以下:
const path = require('path');
module.exports = {
entry: ["babel-polyfill", "./index.js"],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
]
}
};
复制代码
该配置的都配置完了,执行下npm run build
看看效果?果不其然,编译过了。
你觉得工程就这样配完了吗?NO,这仅仅是个开始!
SPA工程中使用,什么是SPA,单页应用?什么是单页应用?只有一个html,公用js css仅引用一次,经过局部刷新渲染的应用。
react-router官网,按着快速上手的步骤来。
yarn add react-router-dom
复制代码
Now you can copy/paste any of the examples into src/App.js. Here’s the basic one:
这里从create-react-app
中使用方式,虽然咱们不是,可是咱们本身搭的也不差啊。本身建个src
和App.js
吧。
mkdir src && touch src/App.js
复制代码
将官方的🌰贴进来App.js
:
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
const Home = () => (
<div> <h2>Home</h2> </div>
)
const About = () => (
<div> <h2>About</h2> </div>
)
const Topic = ({ match }) => (
<div> <h3>{match.params.topicId}</h3> </div>
)
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>
Rendering with React
</Link>
</li>
<li>
<Link to={`${match.url}/components`}>
Components
</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<Route path={`${match.path}/:topicId`} component={Topic}/>
<Route exact path={match.path} render={() => (
<h3>Please select a topic.</h3>
)}/>
</div>
)
const BasicExample = () => (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</Router>
)
export default BasicExample
复制代码
接下来在index.js
中引用:
import React from 'react'
import ReactDOM from 'react-dom';
import App from './src/App';
ReactDOM.render(
<App />, document.getElementById('root') ); 复制代码
执行npm run build
看看。没报错,使用浏览器打开dist/index.html
看看。点击连接没反应?什么状况?莫慌,打开控制台看看:
Uncaught DOMException: Failed to execute 'pushState' on 'History': A history state object with URL 'file:///' cannot be created in a document with origin 'null' and URL
emmmm,咋整?别问我,真不是我干的… 既然chrome很差使,就再看一眼safari下好很差用吧,同样很差使,再看一眼报错信息。
[Error] SecurityError: Blocked attempt to use history.pushState() to change session history URL from file:///demo/dist/index.html to file:///about. Paths and fragments must match for a sandboxed document.
好像safari的报错更友好一些,咱们能够清楚的看到,它在试图改变连接,为了安全起见,你认为这么作合理么?岂不是拿个html能访问计算机任何文件了?emmm,果真不合理。再看关键词: Paths and fragments must match for a sandboxed document
。
那咱们就构建个沙盒环境吧。
yarn add webpack-dev-server --dev
复制代码
在package.json
中添加scripts
:
"dev": "webpack-dev-server"
复制代码
在webpack.config.js
根节点中,添加:
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000
}
复制代码
执行上面刚添加的srcripts
:
npm run dev
复制代码
浏览器中打开连接localhost:9000
功能没问题,样式无法忍,有木有?改!
在src
中新建app.css
文件,新增以下内容:
.container {
list-style: none;
}
复制代码
在App.js
中,添加以下代码:
import './app.css';
复制代码
将BasicExample
中的ul
应用样式:
<ul className="container">
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
复制代码
这时,你会发现一个熟悉的错误。
You may need an appropriate loader to handle this file type.
此次咱们能够很快的定位到,缺乏加载css
相关的loader
。
loader
相遇webpack相关文档 虽然,webpack
的文档老是那么的不及时,可是一些基础性的东西,仍是能从中学到的。既然又一次遇到了loader
,不妨此次咱们就完全搞明白什么是loader
,它为咱们提供了什么?
loader 用于对模块的源代码进行转换。loader 可使你在 import 或"加载"模块时预处理文件。所以,loader 相似于其余构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 能够将文件从不一样的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至容许你直接在 JavaScript 模块中 import CSS文件!
看完这么长一段,我总结了一句话,经过import
来处理的文件,须要对应的loader。既然如此,那就一个一个安吧。
css-loader
yarn add style-loader css-loader --dev
复制代码
修改webpack.config.js
中,module
下的rules
,就像添加babel-loader
同样,添加以下配置:
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
复制代码
关于css-loader这里要再多提一句,当你不想对全局css进行污染,想经过如下方式使用时:
import styles from 'app.css';
<div className={styles.container} /> 复制代码
请使用css module,webpack配置css module的方式也十分简单:
'css-loader?modules&localIdentName=[name]-[hash:base64:5]',
复制代码
将 css-loader
调整成以上内容便可。
file-loader
处理各类图标、图片文件
yarn add file-loader --dev
复制代码
修改webpack.config.js
中,module
下的rules
,添加以下配置:
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
}
复制代码
字体文件依旧由file-loader
处理,继续添加配置:
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
复制代码
loader
暂时添加到这里,这时记住了没?想让webpack
处理对应文件,就要有对应的loader
。
继续刷新看咱们的demo工程,生效了。
loader
配置完,继续按着文档来吧,看看咱们还有什么能够了解的。
插件是 webpack 的支柱功能。webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上!
插件目的在于解决 loader 没法实现的其余事。
管理输出中有这么一段:
若是咱们更改了咱们的一个入口起点的名称,甚至添加了一个新的名称,会发生什么?生成的包将被重命名在一个构建中,可是咱们的index.html文件仍然会引用旧的名字。咱们用 HtmlWebpackPlugin 来解决这个问题。
OK,了解了它的目的,有用,装!
yarn add html-webpack-plugin --dev
复制代码
在webpack.config.js
的根节点中添加plugins
:
// 引入html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
复制代码
plugins: [
new HtmlWebpackPlugin({
title: 'Output Management'
})
],
复制代码
重启下npm run dev
,你会发现页面空白了?卧槽,这不是坑么?打开控制台看一眼,Target container is not a DOM element.
,再看一眼Elements
选项卡中的内容,发现,咦。好像咱们的<div id="root"></div>
神奇的消失了。 再看文档,发现这么一句话:
若是你想要了解更多 HtmlWebpackPlugin 插件提供的所有功能和选项,那么你就应该多多熟悉 HtmlWebpackPlugin 仓库。 其中的配置项中,
template
这项是这么描述的: webpack require path to the template. Please see the docs for details
模板啊,咱们把dist/index.html
中的文件挪出来,放到项目的根目录下,而后再修改下webpack.config.js
中的配置:
new HtmlWebpackPlugin({
title: 'Demo',
template: './index.html'
})
复制代码
重启下服务看看吧,npm run dev
页面出来了,可是报了个错:only one instance of babel-polyfill is allowed
。 这又是什么错?咱们明明只有一个entry
,为何说引了屡次呢?打开Elements
选项卡中,惊奇的发现,原来是咱们刚才直接从dist
目录中挪的index.html
中,还存在<script src="./my-first-webpack.bundle.js"></script>
这么一段script
,删掉,再重启。大功告成。
你可能已经注意到,因为过去的指南和代码示例遗留下来,致使咱们的 /dist 文件夹至关杂乱。webpack 会生成文件,而后将这些文件放置在 /dist 文件夹中,可是 webpack 没法追踪到哪些文件是实际在项目中用到的。
依然有用,依然装!
yarn add clean-webpack-plugin --dev
复制代码
修改webpack.config.js
: 引入CleanWebpackPlugin
:
const CleanWebpackPlugin = require('clean-webpack-plugin');
复制代码
添加plugins
:
new CleanWebpackPlugin(['dist']),
复制代码
为何要区分生产环境与开发环境?
开发环境(development)和生产环境(production)的构建目标差别很大。在开发环境中,咱们须要具备强大的、具备实时从新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,咱们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。因为要遵循逻辑分离,咱们一般建议为每一个环境编写彼此独立的 webpack 配置。
按照官方教程,来进行拆分。在此,我更推荐新建config
目录,将配置统一放置config
中,因此此时咱们的配置文件应该是:
config/webpack.common.js
:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: ["babel-polyfill", "./index.js"],
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].[contenthash:12].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader?cacheDirectory"
},
{
test: /\.css$/,
use: [
'style-loader',
"css-loader"
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(["dist"], {
root: path.resolve(__dirname, "../"),
}),
new HtmlWebpackPlugin({
title: 'Demo',
template: './index.html'
})
]
};
复制代码
config/webpack.dev.js
:
const merge = require('webpack-merge');
const common = require('./webpack.common');
const path = require('path');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: path.resolve(__dirname, '../dist'),
compress: true,
port: 9000
}
});
复制代码
config/webpack.prod.js
:
const merge = require('webpack-merge');
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'production',
});
复制代码
另外,package.json
中的scripts
也要进行相应的调整:
"build": "webpack --config config/webpack.prod.js",
"dev": "webpack-dev-server --config config/webpack.dev.js"
复制代码
tips: 这是一个从入门到放弃的Plugin,感兴趣的话能够继续跟着操做,没兴趣请跳至下个小节。
它会将全部的入口 chunk(entry chunks)中引用的 *.css,移动到独立分离的 CSS 文件。
yarn add extract-text-webpack-plugin --dev
复制代码
照着文档中的🌰把配置贴进来,修为webpack.config.js
:
const path = require('path');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
mode: "production",
entry: ["babel-polyfill", "./index.js"],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader?cacheDirectory"
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
]
},
plugins: [
new ExtractTextPlugin("styles.css"),
],
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000
}
};
复制代码
重启服务的时候,你会发现报错了?WTF?费了半天劲,结果还不能用?这时候咱们须要注意一点,就是它曾经确定是能用的,否则不能放到文档上,这就体现出来webpack
文档落后了。既然如此,由于咱们当下使用的是webpack 4.x
的版本,这时候先去ExtractTextWebpackPlugin的github上搜搜有没有想过issue吧,关键词webpack 4
。 看到一个issue。
@vasivas don't use extract-text-webpack-plugin for extract css, please use github.com/webpack-con…
居然还有这种操做,那就看看这个mini-css-extract-plugin
。
关于webpack,就引导到这里,本文不是对webpack进行讲解,更多关于webpack
的部分,能够看: @花裤衩 写的文章:
最终咱们关于分离css的内容变成以下: webpack.common.js
:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: ["babel-polyfill", "./index.js"],
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].[contenthash:12].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader?cacheDirectory"
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader?modules&localIdentName=[name]-[hash:base64:5]',
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(["dist"], {
root: path.resolve(__dirname, "../"),
}),
new HtmlWebpackPlugin({
title: 'Demo',
template: './index.html'
}),
]
};
复制代码
webpack.prod.js
:
const merge = require('webpack-merge');
const common = require('./webpack.common');
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = merge(common, {
mode: 'production',
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({}) // use OptimizeCSSAssetsPlugin
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash:12].css',
chunkFilename: '[name].[contenthash:12].css' // use contenthash *
})
]
});
复制代码
webpack.dev.js
:
const merge = require('webpack-merge');
const common = require('./webpack.common');
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: path.resolve(__dirname, '../dist'),
compress: true,
port: 9000
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
]
});
复制代码
依旧看上面 @花裤衩 的文章,分的很细腻。在此咱们简单分离:
在webpack.common.js
中修改:
const merge = require('webpack-merge');
const common = require('./webpack.common');
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = merge(common, {
mode: 'production',
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({}) // use OptimizeCSSAssetsPlugin
],
runtimeChunk: {
name: "manifest"
},
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
priority: -20,
chunks: "all"
}
}
}
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash:12].css',
chunkFilename: '[name].[contenthash:12].css' // use contenthash *
})
]
});
复制代码
OK,看似完美。
React
、Babel
、webpack
都有了,接下来就是为了本身与团队之间协做代码的规范性,要经过下一个工具了。
npm install eslint -g #全局安装eslint
复制代码
eslint --init
复制代码
? How would you like to configure ESLint? Use a popular style guide
? Which style guide do you want to follow? Airbnb (https://github.com/airbnb/javascript)
? Do you use React? Yes
? What format do you want your config file to be in? JavaScript
Checking peerDependencies of eslint-config-airbnb@latest
? The style guide "airbnb" requires eslint@^4.19.1. You are currently using eslint@5.2.0.
Do you want to downgrade? Yes
复制代码
在此直接选择airbnb
。
在初次安装后,咱们发现咱们以前的App.js
报错了,这时咱们须要调整eslint
相关的配置规则,来让它更符合咱们预期的使用: 打开.eslintrc.js
文件,调整内容以下:
module.exports = {
"extends": "airbnb",
"plugins":[
"react",
"jsx-a11y",
"import"
],
"rules": {
"import/no-extraneous-dependencies": "off",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/prop-types": 0
}
};
复制代码
更多eslint
的使用姿式,还须要你我的进行探索。
ant-design
yarn add antd
复制代码
若不想每次引用css时,可选用,在这里使用babel-plugin-import
:
yarn add babel-plugin-import --dev
复制代码
修改.babelrc
文件中修改成:
{
"presets": ["env", "react"],
"plugins": [
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
]
}
复制代码
在App.js
中,进行引用:
import { DatePicker } from 'antd';
const Home = () => (
<div> <h2> <DatePicker /> </h2> </div>
);
复制代码
刷新后咱们发现,组件是显示出来了,可是样式并无生效。继续去找解决方案: www.jianshu.com/p/603a61471… 这位老哥写的很清楚了,原来是咱们的rules
配置还有点瑕疵,根据内容调整以下:
{
test: /\.css$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
'css-loader?modules&localIdentName=[name]-[hash:base64:5]',
],
},
{// antd样式处理
test: /\.css$/,
exclude: /src/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
],
},
复制代码
调整完后,重启webpack
。
本文只作引子,让你对前端工程化有必定的了解,更多的还需本身去根据关键字探索。
本人上上周搞得的基于百度地图的封装现已开源,欢迎加入一块儿搞事情: