React+TypeScript+webpack4多入口项目搭建

资源

  • React-16.8.*
  • react-router-dom-4.3.*
  • TypeScript-3.5.*
  • webpack-4.*
  • eslint-5.16.*

项目目录

├── dist # 打包结果目录
│   ├── demo1 //类别demo1的打包结果
│	│	├── demo1.himl
│   │	├── demo1.js
│	│	└── demo1.css
│   └── demo2 ... //类别demo2的打包结果
├── src # 业务资源文件目录
│	├── category //项目分类
│	│	├── demo1
│	│	├── demo2
│	│	└── ...
│	├── components //公共组件
│	├── util //公共资源
│	└── custom.d.ts //项目全局变量声明文件
├── index.html //项目启动入口
├── .gitignore //git忽略文件
├── .eslintrc.js //eslint校验配置
├── package.json //依赖包
├── tsconfig.json //ts配置
├── webpack.config.build.js //webpack打包
├── webpack.config.base.js //webpack基础配置
└── webpack.config.js //项目启动配置

前言

对于复杂或多人开发的 React 项目来说,管理和使用每个组件的 propsstate 或许会成为一件让人头痛的事情,而为每一个组件写文档,成本也会比较大,对项目的开发效率也不是最理想的。

TypescriptReact 带来很多好处:

  • 在组件头部定义 interface,让每个人在拿到组件的第一时间就可以很明确知道该组件需要使用的 propsstate
  • 在编译中发现问题,减少运行时的报错;
  • 可以在编辑器中实现实时类型校验、引用查询;
  • 约束类型,在混合多语言环境中降低风险,等。

需求

要搭建一个React+TypeScript+webpack的项目的话,一般都是团队开发多人多文件项目,在搭建之前需要优先考虑以下几个方面:

  • 开发体验
  • 项目打包
  • 团队规范

安装

  • 前置安装
    首先需要全局安装typescript,这里默认大家都已经安装了node以及npm

    npm install -g typescript
    
  • 首先新建文件夹并进入

    mkdir tsDemo && cd tsDemo
    
  • 然后进行初始化,生成package.jsontsconfig.json

    npm init -y && tsc --init
    
  • 安装开发工具

    npm install-D webpack webpack-cli webpack-dev-server
    
  • 安装react相关
    因为需要整合ts,而react原本的包是不包含验证包的,所以这里也需要安装相关ts验证包

    npm install -S react react-dom
    npm install -D @types/react @types/react-dom
    
  • 安装ts-loader

    npm install -D ts-loader
    
  • 以上是基本的 后续会贴出项目demo里面包含所有依赖包

webpack配置

添加webpack文件

根目录下新建webpack.config.base.js、webpack.config.build.js、webpack.config.js文件

touch webpack.config.base.js webpack.config.build.js webpack.config.js
  1. entry:入口文件(你要打包,就告诉我打包哪些)
  2. output:出口文件(我打包完了,给你放到哪里)
  3. resolve: 寻找模块所对应的文件
  4. module:模块(放lorder,编译浏览器不认识的东西)
  5. plugins:插件(辅助开发,提高开发效率)
  6. externals:打包忽略
  7. devServer:服务器(webpack提供的本地服务器)
  8. mode:模式,分为开发模式、生产模式。此为4.X里新增的

配置entry入口文件

因为大部分项目是多入口,多类别的,所有入口配置时不要配置单一入口

const fs = require("fs");
const path = require("path");
const optimist = require("optimist");

const cateName = optimist.argv.cate;
let entryObj = {};
const srcPath = `${__dirname}/src`;
//获取当前项目要启动或者打包的基础路径
const entryPath = `${srcPath}/category/`;
//未指定类别 启动或者打包所有类别
//如:npm run dev 或者npm run build
if (cateName == true) {
    fs.readdirSync(entryPath).forEach((cateName, index) => {
        // cateName/cateName指定输出路径为entryname
        if (cateName != "index.html" && cateName != ".DS_Store") entryObj[`${cateName}/${cateName}`] = `${entryPath + cateName}/${cateName}.tsx`;
    });
} else if (cateName.indexOf(",")) {
    // 一次指定多个类别 类别之间以","分割
    //如:npm run dev erhsouche,huoche 
    let cateNameArray = cateName.split(",");
    for (let i = 0; i < cateNameArray.length; i++) {
        entryObj[`${cateNameArray[i]}/${cateNameArray[i]}`] = `${entryPath + cateNameArray[i]}/${
            cateNameArray[i]
        }.tsx`;
    }
} else {
    // 打包单个入口文件
    //如:npm run dev ershouche
    entryObj[`${cateName}/${cateName}`] = `${entryPath + cateName}/${cateName}.tsx`;
}
const webpackConfig = {
    entry: entryObj,
}
module.exports = {
    webpackConfig,
    entryObj
};

配置output出口文件

const webpackConfig = {
    output: {
    	//输出文件名称以当前传入的cate类别名称命名
        filename: "[name].js", 
        //输出到根目录下的dist目录中
        path: path.resolve(__dirname, "dist"),
        publicPath: "/",
    },
}

配置resolve

需要import xxx from 'xxx'这样的文件的话需要在webpack中的resolve项中配置extensions,这样以后引入文件就不需要带扩展名

const webpackConfig = {
    resolve: {
        extensions: [".tsx", ".ts", ".js", ".jsx", ".json"],
        //配置项通过别名来把原导入路径映射成一个新的导入路径。
        alias: {
            images: path.join(__dirname, "src/util/img")
        },
        // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
        modules: [path.resolve(__dirname, "node_modules")] 
    },
}

配置module

概念

webpack中任何一个东西都称为模块,js就不用说了。一个css文件,一张图片、一个less文件都是一个模块,都能用导入模块的语法(commonjsrequireES6import)导入进来。webpack自身只能读懂js类型的文件,其它的都不认识。但是webpack却能编译打包其它类型的文件,像ES6JSXlesstypeScript等,甚至cssimages也是Ok的,而想要编译打包这些文件就需要借助loader

loader就像是一个翻译员,浏览器不是不认识这些东西么?那好交给loader来办,它能把这些东西都翻译成浏览器认识的语言。loader描述了webpack如何处理非js模块,而这些模块想要打包loader必不可少,所以它在webpack里显得异常重要。loader跟插件一样都是模块,想要用它需要先安装它,使用的时候把它放在module.rules参数里,rules翻译过来的意思就是规则,所以也可以认为loader就是一个用来处理不同文件的规则

所需loader

  • ts-loader

    编译TypeScript文件

    npm install ts-loader -D
    
  • url-loader

    处理css中的图片资源时,我们常用的两种loader是file-loader或者url-loader,两者的主要差异在于。url-loader可以设置图片大小限制,当图片超过限制时,其表现行为等同于file-loader,而当图片不超过限制时,则会将图片以base64的形式打包进css文件,以减少请求次数。

    npm install url-loader -D
    
  • css处理所需loader
    css-loader 处理css

    sass-loader 编译处理scss

    sass-resources-loader 全局注册变量

  • html-loader
    处理.html文件

module完整配置

const webpackConfig = {
    module: {
        rules: [
        	//处理tsx文件
            { test: /\.(tsx|ts)?$/, use: ["ts-loader"], include: path.resolve(__dirname, "src") },
            //处理图片资源
            {
                test: /\.(png|jpe?g|jpg|gif|woff|eot|ttf|svg)/,
                use: [
                    // 对非文本文件采用file-loader加载
                    {
                        loader: "url-loader",
                        options: {
                            limit: 1024 * 30, // 30KB以下的文件
                            name: "images/[name].[hash:8].[ext]",
                        }
                    }
                ],
            },
            //处理css和scss
            {
                test: /\.(css|scss)$/,
                use: [
                	//css单独打包
                    MiniCssExtractPlugin.loader,
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: "postcss-loader",
                        options: {
                            plugins: () => [require("autoprefixer")],
                            sourceMap: true
                        }
                    },
                    {
                        loader: "sass-loader",
                        options: {
                            sourceMap: true
                        }
                    },
                    {
                        loader: "sass-resources-loader",
                        options: {
                            resources: ["./skin/mixin.scss", "./skin/base.scss"]
                        }
                    }
                ],
                exclude: path.resolve(__dirname, "node_modules")
            },
            {
                test: /\.html$/,
                use: {
                    loader: "html-loader",
                }
            },
            { test: /src\/containers(\/.*).(tsx|ts)/, loader: "bundle-loader?lazy!ts-loader" },
            { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
        ]
    },
}

配置plugins

plugins里面放的是插件,插件的作用在于提高开发效率,能够解放双手,让我们去做更多有意义的事情。一些很low的事就统统交给插件去完成。

const webpackConfig = {
    plugins: [
    	//清除文件
        new CleanWebpackPlugin(),
        //css单独打包
        new MiniCssExtractPlugin({
            filename: "[name].css",
            chunkFilename: "[name].css"
        }),
        // 引入热更新插件
        new webpack.HotModuleReplacementPlugin() 
    ]
}

配置externals

如果需要引用一个库,但是又不想让webpack打包(减少打包的时间),并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用(一般都以import方式引用使用),那就可以通过配置externals。

const webpackConfig = {
	//项目编译打包是忽略这些依赖包
    externals: {
        react: "React",
        "react-dom": "ReactDOM",
        "react-redux": "ReactRedux",
    }
}

配置mode

modewebpack4新增的一条属性,它的意思为当前开发的环境。mode的到来减少了很多的配置,它内置了很多的功能。相较以前的版本提升了很多,减少了很多专门的配置

  1. 提升了构建速度
  2. 默认为开发环境,不需要专门配置
  3. 提供压缩功能,不需要借助插件
  4. 提供SouceMap,不需要专门配置

mode分为两种环境,一种是开发环境(development),一种是生产环境(production)。开发环境就是我们写代码的环境,生产环境就是代码放到线上的环境。这两种环境的最直观区别就是,开发环境的代码不提供压缩,生产环境的代码提供压缩。

配置devServer

const webpackConfig = {
    devServer: {
    	// 本地服务器所加载的页面所在的目录
        contentBase: srcPath, 
        //热更新
        hot: true,
        //服务端口
        port: "7788",
        // 是否向Chunk中注入代理客户端,默认注入
        inline: true, 
        // publicPath: '/dist/',
        historyApiFallback: {
            index: "template.html",
        },
        //默认检查hostname
        disableHostCheck: true,
        compress: true,
        open: true // 自动打开首页
    }
}

webpack.config.base.js完整文件

const fs = require("fs");
const path = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlguin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // webpack4支持的单独打包css
// const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); //webpack4支持自动优化代码 mode:'development'即可
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

const isProduction = process.env.NODE_ENV === "production";

const optimist = require("optimist");

const cateName = optimist.argv.cate;
let entryObj = {};
const srcPath = `${__dirname}/src`;
const entryPath = `${srcPath}/category/`;
if (cateName == true) {
    fs.readdirSync(entryPath).forEach((cateName, index) => {
        // cateName/cateName指定输出路径为entryname
        if (cateName != "index.html" && cateName != ".DS_Store") entryObj[`${cateName}/${cateName}`] = `${entryPath + cateName}/${cateName}.tsx`;
    });
} else if (cateName.indexOf(",")) {
    // 一次打包多个入口文件以逗号分隔
    let cateNameArray = cateName.split(",");
    for (let i = 0; i < cateNameArray.length; i++) {
        entryObj[`${cateNameArray[i]}/${cateNameArray[i]}`] = `${entryPath + cateNameArray[i]}/${
            cateNameArray[i]
        }.tsx`;
    }
} else {
    // 打包单个入口文件
    entryObj[`${cateName}/${cateName}`] = `${entryPath + cateName}/${cateName}.tsx`;
}

const webpackConfig = {
    entry: entryObj,
    output: {
        filename: "[name].js",
        path: path.resolve(__dirname, "dist"),
        publicPath: "/",
    },
    resolve: {
        extensions: [".tsx", ".ts", ".js", ".jsx", ".json"],
        alias: {
            images: path.join(__dirname, "src/util/img"),
            components: path.join(__dirname, "src/components")
        },
        modules: [path.resolve(__dirname, "node_modules")] // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    },
    module: {
        rules: [
            { test: /\.(tsx|ts)?$/, use: ["ts-loader"], include: path.resolve(__dirname, "src") },
            {
                test: /\.(png|jpe?g|jpg|gif|woff|eot|ttf|svg)/,
                use: [
                    // 对非文本文件采用file-loader加载
                    {
                        loader: "url-loader",
                        options: {
                            limit: 1024 * 30, // 30KB以下的文件
                            name: "images/[name].[hash:8].[ext]",
                        }
                    }
                ],
            },
            {
                test: /\.(css|scss)$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: "postcss-loader",
                        options: {
                            plugins: () => [require("autoprefixer")],
                            sourceMap: true
                        }
                    },
                    {
                        loader: "sass-loader",
                        options: {
                            sourceMap: true
                        }
                    },
                    {
                        loader: "sass-resources-loader",
                        options: {
                            resources: ["./skin/mixin.scss", "./skin/base.scss"]
                        }
                    }
                ],
                exclude: path.resolve(__dirname, "node_modules")
            },
            {
                test: /\.html$/,
                use: {
                    loader: "html-loader",
                }
            },
            { test: /src\/containers(\/.*).(tsx|ts)/, loader: "bundle-loader?lazy!ts-loader" },
            { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
        ]
    },
    mode: isProduction ? "development" : "production",
    // 安全起见,生产环境使用hidden-source-map,会生成详细的Source Map,但不会将Source Map暴露出去
    devtool: isProduction ? "hidden-source-map" : "cheap-module-eval-source-map",
    externals: {
        react: "React",
        "react-dom": "ReactDOM",
        "react-redux": "ReactRedux",
    },
    plugins: [
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: "[name].css",
            chunkFilename: "[name].css"
        }),
        new webpack.HotModuleReplacementPlugin() // 引入热更新插件
    ]
};

const pages = Object.keys(entryObj);

pages.forEach(pathname => {
    const htmlName = entryObj[pathname];
    const template_local = htmlName.replace(".tsx", ".html");
    const entryName = pathname.split("/")[0];
    let conf = {
        filename: `category/${entryName}/${entryName}.html`, // 生成的html存放路径,相对于path
        title: entryName,
        template: template_local, // html模板路径
        inject: true, // js插入的位置,true/'head'/'body'/false
        hash: true, // 为静态资源生成hash值
        // favicon: 'src/favicon.ico', //favicon路径,通过webpack引入同时可以生成hash值
        chunks: [pathname],
        minify: {
            // 压缩HTML文件
            removeComments: true, // 移除HTML中的注释
            collapseWhitespace: false, // 删除空白符与换行符
        },
    };
    const defineConf = Object.assign({}, conf, { template: "src/template.html" });
    const exists = fs.existsSync(template_local);
    if (exists) {
        webpackConfig.plugins.push(new HtmlWebpackPlguin(conf));
    } else {
        webpackConfig.plugins.push(new HtmlWebpackPlguin(defineConf));
    }
});

module.exports = {
    webpackConfig,
    entryObj
};

webpack.config.js完整文件

const webpack = require("webpack");
const { webpackConfig } = require("./webpack.config.base");
let optimist = require("optimist");

let cateName = optimist.argv.cate;
const srcPath = `${__dirname}/src`;
webpackConfig.devServer = {
    contentBase: srcPath, // 本地服务器所加载的页面所在的目录
    hot: true,
    port: "7788",
    inline: true, // 是否向Chunk中注入代理客户端,默认注入
    // publicPath: '/dist/',
    historyApiFallback: {
        index: "template.html",
    },
    disableHostCheck: true,
    compress: true,
    open: true // 自动打开首页
};
// 通过插件模式开启模块热替换 也可在执行命令时加上 --hot
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
module.exports = webpackConfig;

发布了58 篇原创文章 · 获赞 78 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/gongch0604/article/details/98063159