從零搭建React開發腳手架,基於[email protected]
前言
[email protected]已經遷移到4,將目前前端最主流,應用最廣的webpack總結下
目前兩個常用的構建工具
-
facebook官方的create-react-app(官方推薦)
基於[email protected]的版本 -
社群的generator-react-webpack(可配置性高)
基於[email protected]的版本
(二)webpack基本概念
- 入口(entry)
- 輸出(output)
- loader
- 外掛(plugins)
- entry
webpack 應該使用哪個模組,來作為構建其內部依賴圖的開始。進入入口起點後,webpack 會找出有哪些模組和庫是入口起點(直接和間接)依賴的。
- output
output 屬性告訴 webpack 在哪裡輸出它所建立的 bundles,以及如何命名這些檔案。
配置引數
-
loader
webpack loader 將所有型別的檔案,轉換為應用程式的依賴圖(和最終的 bundle)可以直接引用的模組。配置中 loader 有兩個目標:
test 屬性,用於標識出應該被對應的 loader 進行轉換的某個或某些檔案。
use 屬性,表示進行轉換時,應該使用哪個 loader。
- plugins
外掛目的在於解決 loader 無法實現的其他事。比如 壓縮js(uglifyjs-webpack-plugin),壓縮css(optimize-css-assets-webpack-plugin),將js中的css分離獨立出來(extract-text-webpack-plugin)等
(三)搭建webpack基礎環境
- 初始化專案目錄
$ mkdir webpack-demo
$ cd webpack-demo
$ npm init -y // -y的意思是預設安裝
新建四個檔案,分別是webpack.base.js(基礎環境),webpack.dev.js(開發環境),webpack.prod.js(生產環境),.gitignore(git忽略檔案)
這時候我們的檔案目錄為
- 配置webpack基本環境設定
注意:-S是安裝在生產環境,-D安裝在開發環境。
// 模組管理和打包工具
$ npm install [email protected] -D
// 監聽程式碼自動重新整理(注意@3版本對應 [email protected])
$ npm install [email protected] -D
// 安裝merge
$ npm i webpack-merge -D
// 安裝html處理
$ npm i html-webpack-plugin -D
// 清除外掛
$ npm i clean-webpack-plugin -D
// copy 外掛
$ npm i copy-webpack-plugin -D
// 安裝dev open-browser
$ npm i open-browser-webpack-plugin -D
// 安裝 lodash
$ npm install lodash -S
新建public和src資料夾,專案目錄為下圖
配置webpack.base.js
/**
* @component webpack.base.js
* @description 基礎環境
* @time 2018/3/8
* @author **
*/
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 生成html
const CleanWebpackPlugin = require('clean-webpack-plugin'); // 清除dist
const CopyWebpackPlugin = require('copy-webpack-plugin'); // copy
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
entry: {
app: './src/index.js',
vendor: [
'lodash'
]
},
resolve: {
extensions: [' ', '.js', '.json', '.jsx', '.css', '.less','.json'],
modules: [resolve( "src"), "node_modules"], //絕對路徑;
},
module: {
},
plugins: [
new CleanWebpackPlugin(['dist']),
new CopyWebpackPlugin([{
from: "./public",
to: "",
force: true
}]),
new HtmlWebpackPlugin({
filename: 'index.html',//輸出的html路徑
template: './public/index.html', //html模板路徑
chunks: ['app', 'vendor', 'manifest'],
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyCSS: true,
minifyJS: true,
minifyURLs: true,
}
}),
new webpack.HashedModuleIdsPlugin(), // 修復vendor hash
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest', // 指定公共 bundle 的名稱。
minChunks: Infinity
})
]
};
配置webpack.dev.js
/**
* @component webpack.dev.js
* @description 開發環境
* @time 2018/3/8
* @author **
*/
const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');
const OpenBrowserPlugin = require('open-browser-webpack-plugin');
const base = require('./webpack.base.js');
function resolve (dir) {
return path.join(__dirname, dir)
}
// 埠
const port = 8080;
module.exports = merge(base, {
output: {
filename: 'static/js/[name].js', //
path: resolve('dist'), // 輸出的檔案地址
publicPath: ''
},
devtool: 'inline-source-map',
module: {
},
devServer: {
contentBase: './dist',
compress: true,
port: port,
historyApiFallback: true,//不跳轉
inline: true//實時重新整理
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
new OpenBrowserPlugin({ url: 'http://localhost:'+port }),
],
});
配置webpack.prod.js
/**
* @component webpack.prod.js
* @description 生產環境
* @time 2018/3/8
* @author **
*/
const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');
const base = require('./webpack.base.js');
function resolve (dir) {
return path.join(__dirname, dir)
}
module.exports = merge(base, {
output: {
filename: 'static/js/[name].[hash:7].js', //
path: resolve('dist'), // 輸出的檔案地址
publicPath: './'
},
devtool: 'source-map',
module: {
},
plugins: [
new webpack.optimize.UglifyJsPlugin({ // 壓縮JS
compress: {
warnings: false,
comparisons: false,
},
mangle: {
safari10: true,
},
output: {
comments: false,
ascii_only: true,
},
sourceMap: true,
}),
]
});
src新增index.js
import _ from 'lodash';
function component() {
var element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
return element;
}
document.body.appendChild(component());
public新增index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Getting Started</title>
</head>
<body>
</body>
</html>
修改package.json
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
分別執行 npm run dev(開發) / npm run build(釋出)
演示目的,我們使用開發演示修改程式碼。
(四)增加css處理
// 安裝相關loader
$ npm install style-loader css-loader less-loader postcss-loader postcss-flexbugs-fixes -D
$ npm i autoprefixer -D
$ npm i less -D
// 將css分離出js
$ npm i extract-text-webpack-plugin -D
配置webpack.prod.js
const webpack = require('webpack');
const autoprefixer = require('autoprefixer');
const path = require('path');
const merge = require('webpack-merge');
const ExtractTextPlugin = require("extract-text-webpack-plugin"); // 分離css
const base = require('./webpack.base.js');
function resolve (dir) {
return path.join(__dirname, dir)
}
module.exports = merge(base, {
output: {
filename: 'static/js/[name].[hash:7].js', //
path: resolve('dist'), // 輸出的檔案地址
publicPath: './'
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallback: {
loader: require.resolve('style-loader'),
options: {
hmr: false,
},
},
use: [
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
minimize: true,
sourceMap: true,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
],
}),
},
{
test: /\.less$/,
loader: ExtractTextPlugin.extract({
fallback: {
loader: require.resolve('style-loader'),
options: {
hmr: false,
},
},
use: [
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
minimize: true,
sourceMap: true,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
{
loader: require.resolve('less-loader'),
options: {
importLoaders: 2,
minimize: true,
sourceMap: true,
},
},
],
}),
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
},
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({ // 壓縮JS
compress: {
warnings: false,
comparisons: false,
},
mangle: {
safari10: true,
},
output: {
comments: false,
ascii_only: true,
},
sourceMap: true,
}),
new ExtractTextPlugin({
filename: "static/css/[name].[hash:7].css",
allChunks: true
}),
]
});
配置webpack.dev.js
const webpack = require('webpack');
const autoprefixer = require('autoprefixer');
const path = require('path');
const merge = require('webpack-merge');
const OpenBrowserPlugin = require('open-browser-webpack-plugin');
const base = require('./webpack.base.js');
function resolve (dir) {
return path.join(__dirname, dir)
}
// 埠
const port = 8080;
module.exports = merge(base, {
output: {
filename: 'static/js/[name].js', //
path: resolve('dist'), // 輸出的檔案地址
publicPath: ''
},
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
],
},
{
test: /\.less$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
{
loader: require.resolve('less-loader'),
options: {
importLoaders: 2,
},
},
],
},
]
},
devServer: {
contentBase: './dist',
compress: true,
port: port,
historyApiFallback: true,//不跳轉
inline: true//實時重新整理
},
plugins: [
new OpenBrowserPlugin({ url: 'http://localhost:'+port }),
],
});
src修改
新增index.css, test.less
/**index.css**/
body {
background-color: aqua;
}
/**test.less**/
.hello {
color: red;
transition: all .5s;
}
/**index.js新增**/
import './index.css'
import './test.less'
npm run build檢視打包dist資料夾
(五)增加image, font, babel處理, 壓縮js,react
// image和font處理
$ npm i file-loader url-loader -D
//減少打包的時候重複程式碼
$ npm i babel-runtime -S
$ npm i babel-plugin-transform-runtime -D
//安裝babel相關
$ npm i babel-loader -D //安裝babel-loader
$ npm i babel-core -D //安裝babel核心
$ npm i babel-preset-es2015 -D //支援ES2015
$ npm i babel-preset-react -D //支援jsx
$ npm i babel-preset-stage-0 -D //支援ES7
//安裝react
$ npm i react -S
$ npm i react-dom -S
專案結構
webpack.base.js新增module,修改entry
entry: {
app: './src/index.js',
vendor: [
'react',
'react-dom'
]
},
module: {
rules: [
{
test: /\.(js|jsx)?$/,
loader: 'babel-loader',
exclude: /node_modules/,
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|svg|jpg|gif|jpeg)$/, use:
[{
loader: 'url-loader',
options: {
fallback: 'file-loader',
limit: 8192,
outputPath: 'static/images/',
name: '[name]_[hash:7].[ext]',
}
}]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/, use:
[{
loader: 'file-loader',
options: {
outputPath: 'static/fonts/',
name: '[name]_[hash:7].[ext]',
}
}]
}
]
},
在public/index.html
<body>
<!--新增-->
<div id="root"></div>
</body>
</html>
新建.babel
{
"presets": [ "es2015", "stage-0", "react"],
"env": {
"build": {
"optional": ["optimisation", "minification"]
}
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import _ from 'lodash';
import './index.less';
if (process.env.NODE_ENV !== 'production') {
console.log('Looks like we are in development mode!!!!');
}
ReactDOM.render(<App />, document.getElementById('root'));
App.jsx
import React, { Component } from 'react';
import Icon from './logo.svg';
import loginBg from './login-bg.jpg';
import './App.less';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={Icon} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
<img src={loginBg}/>
</p>
</div>
);
}
}
export default App;
相關程式碼已推送到github