前言:相信大家看到过不少web音乐app,什么仿网易云、QQ音乐之类的。笔者带大家自制一款web音乐app
本项目使用react全家桶打造,技术综合性比较高。很多东西不会从基础讲解
技术要求
- react (版本16.2.0,基础熟练)
- react-router (4.2.2,掌握基础)
- react-redux (5.0.6,掌握基础)
- es6 (掌握基础)
- webpack (3.8.1,掌握基础)
- html5
- css3
创建项目
使用create-react-app创建项目基本骨架,然后安装路由(由于4.x的版本路由变化比较大。路由安装react-router-dom 4.2.2的版本即可)
创建后的项目结构如下
如何使用自己的webpack配置?
在react脚手架中webpack基本的配置官方都已经给我们配置好了,那么如何加入我们自定义的配置文件呢,这里使用一个rewire模块。 首先安装rewire 模块
npm install rewire proxyquire --save-dev
rewire只需要在项目开发时打包使用,安装到开发依赖即可
编写配置文件
在项目的根目录下创建scripts文件夹,然后新建一个customized-build.js文件 代码如下:
/*
本模块运行react-scripts里的脚本 (Create React App)
可以自定义webpack配置,通过在项目根目录创建"overrides-config.dev.js" 、 "overrides-config.prod.js" 文件.
A config-overrides file should export a single function that takes a
config and modifies it as necessary.
module.exports = function(webpackConfig) {
webpackConfig.module.rules[0].use[0].options.useEslintrc = true;
};
*/
var rewire = require('rewire');
var proxyquire = require('proxyquire');
switch(process.argv[2]) {
// The "start" script is run during development mode
case 'start':
rewireModule('react-scripts/scripts/start.js', loadCustomizer('./overrides-config.dev'));
break;
// The "build" script is run to produce a production bundle
case 'build':
rewireModule('react-scripts/scripts/build.js', loadCustomizer('./overrides-config.prod'));
break;
// The "test" script runs all the tests with Jest
case 'test':
// Load customizations from the config-overrides.testing file.
// That file should export a single function that takes a config and returns a config
let customizer = loadCustomizer('./overrides-config.testing');
proxyquire('react-scripts/scripts/test.js', {
// When test.js asks for '../utils/createJestConfig' it will get this instead:
'../utils/createJestConfig': (...args) => {
// Use the existing createJestConfig function to create a config, then pass
// it through the customizer
var createJestConfig = require('react-scripts/utils/createJestConfig');
return customizer(createJestConfig(...args));
}
});
break;
default:
console.log('customized-build only supports "start", "build", and "test" options.');
process.exit(-1);
}
// Attempt to load the given module and return null if it fails.
function loadCustomizer(module) {
try {
return require(module);
} catch(e) {
if(e.code !== "MODULE_NOT_FOUND") {
throw e;
}
}
// If the module doesn't exist, return a
// noop that simply returns the config it's given.
return config => config;
}
function rewireModule(modulePath, customizer) {
// Load the module with `rewire`, which allows modifying the
// script's internal variables.
let defaults = rewire(modulePath);
// Reach into the module, grab its global 'config' variable,
// and pass it through the customizer function.
// The customizer should *mutate* the config object, because
// react-scripts imports the config as a `const` and we can't
// modify that reference.
let config = defaults.__get__('config');
customizer(config);
}
上述代码的作用就是在运行时获取dev、build期间对应模块配置。react脚手架自带的脚本 npm run start,npm run build运行的webpack配置文件 存放在node_modules/react-scripts/config下面
webpack.config.dev.js开发时的配置文件
webpack.config.prod.js生产打包时的配置文件
function rewireModule(modulePath, customizer) {
// Load the module with `rewire`, which allows modifying the
// script's internal variables.
let defaults = rewire(modulePath);
// Reach into the module, grab its global 'config' variable,
// and pass it through the customizer function.
// The customizer should *mutate* the config object, because
// react-scripts imports the config as a `const` and we can't
// modify that reference.
let config = defaults.__get__('config');
customizer(config);
}
上述代码中的config就是webpack.config.dev.js(开发时)或webpack.config.prod.js(生产打包时)中module.exports的对象。有了这个对象我们可以对它做添加配置的操作
配置Stylus预处理语言
因为stylus就是为了node环境而打造,语法和css几乎相差不大。所有该项目使用stylus作为css预处理语言。stylus的github地址:github.com/stylus/styl…
首先安装stylus模块
npm install stylus stylus-loader --save-dev
模块安装完成后,在scripts文件夹下面新建overrides-config.base.js文件,同时为开发、和生产打包时创建overrides-config.dev.js和overrides-config.prod.js文件
overrides-config.dev.js
module.exports = function(config) {
// Use your ESLint
/*let eslintLoader = config.module.rules[0];
eslintLoader.use[0].options.useEslintrc = true;*/
// Add the stylus loader second-to-last
// (last one must remain as the "file-loader")
let loaderList = config.module.rules[1].oneOf;
loaderList.splice(loaderList.length - 1, 0, {
test: /\.styl$/,
use: ["style-loader", "css-loader", "stylus-loader"]
});
};
overrides-config.prod.js
const paths = require('react-scripts/config/paths');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath;
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
const cssFilename = 'static/css/[name].[contenthash:8].css';
const extractTextPluginOptions = shouldUseRelativeAssetPaths
? // Making sure that the publicPath goes back to to build folder.
{ publicPath: Array(cssFilename.split('/').length).join('../') }
: {};
module.exports = function(config) {
// Use your ESLint
/*let eslintLoader = config.module.rules[0];
eslintLoader.use[0].options.useEslintrc = true;*/
// Add the stylus loader second-to-last
// (last one must remain as the "file-loader")
let loaderList = config.module.rules[1].oneOf;
loaderList.splice(loaderList.length - 1, 0, {
test: /\.styl$/,
loader: ExtractTextPlugin.extract(
Object.assign(
{
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('stylus-loader')
}
]
}
), extractTextPluginOptions)
});
};
修改启动脚本
打开package.json文件
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
将start和build脚本修改如下
"scripts": {
"start": "node scripts/customized-build start",
"build": "node scripts/customized-build build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
测试webpack配置
将项目下的App.css修改成App.styl,去掉里面的花括号和分号
.App
text-align: center
.App-logo
animation: App-logo-spin infinite 20s linear
height: 80px
.App-header
background-color: #222
height: 150px
padding: 20px
color: white
.App-title
font-size: 1.5em
.App-intro
font-size: large
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
运行命令npm start,此时运行的是自定义的配置文件
查看地址**http://localhost:3000**页面上内容是否与第一次创建时是否一致,如果没有任何变化说明没有问题
加入autoprefixer
autoprefixer是用来处理css厂商前缀的一款插件。react脚手架已经依赖了autoprefixer插件,所以npm已经为我们安装了autoprefixer
为了stylus和autoprefixer一起使用这里使用一款poststylus插件,github地址:github.com/seaneking/p…。安装poststylus
npm install poststylus --save-dev
在overrides-config.base.js配置文件,增加poststylus插件配置
const webpack = require('webpack');
const poststylus = require('poststylus');
const autoprefixer = require('autoprefixer');
module.exports.stylusLoaderOptionsPlugin = new webpack.LoaderOptionsPlugin({
options: {
stylus: {
use: [
poststylus([
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
})
])
]
}
}
});
然后在overrides-config.dev.js和overrides-config.prod.js中导入overrides-config.base.js,在model.exports函数中增加以下代码
// Use Poststylus Plugin to handle stylus
config.plugins.push(baseConfig.stylusLoaderOptionsPlugin);
未使用poststylus插件时,我们查看Logo图片的样式
使用后
已经为我们自动添加了前缀
为项目配置根路径别名
在使用相对路径的时候如果层级很深会非常麻烦,这个时候配置根路径别名就很方便的使用绝对路径了 在overrides-config.base.js中增加以下代码
const path = require('path');
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports.rootPath = resolve('src');
最后在overrides-config.dev.js和overrides-config.prod.js的model.exports函数中增加
// Define the root path alias
let alias = config.resolve.alias;
alias["@"] = baseConfig.rootPath;
overrides-config.base.js完整配置如下
const path = require('path');
const webpack = require('webpack');
const poststylus = require('poststylus');
const autoprefixer = require('autoprefixer');
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports.rootPath = resolve('src');
module.exports.stylusLoaderOptionsPlugin = new webpack.LoaderOptionsPlugin({
options: {
stylus: {
use: [
poststylus([
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
})
])
]
}
}
});
最后
完整项目地址:github.com/dxx/mango-m…
本章节代码在chapter1分支